第3回
ソースの入力から完成まで

実行形式ファイルができるまで

ここで、ソースファイルから実行形式ファイルができあがるまでの流れを見ておきましょう。あくまでコンソールモードのCで開発する場合ですが、基本はコンパイラ型のどのような言語でも同じです。

簡単なプログラムを作る

まず、簡単なプログラムを作ってみましょう。リスト1のようなソースです。リスト1aはLSI-CやGCCなど標準的なC処理系向けのソース、リスト1bはVisual C++(VC++)のコンソールアプリケーション用のソースです。VC++でプロジェクトを作ると、あらかじめ_tmain関数の外枠が準備されるので、網掛けした箇所だけを入力します。

ソースコードの詳しい説明は次回に譲ることにして、今は簡単にプログラムの動作を説明しておきます。

printf関数は文字列を表示する機能、scanf関数はキーボードからの入力を受け付ける機能です ※4

scanf("%d", &num);
の1行でプログラムは入力待ちとなり、キーボードから入力された数値がint型の変数numに保存されます。numの前に着いている“&”記号は、変数numの場所を示します。

ビルドして実行すると、画面に
Input number :
と表示されます。適当な数値(例えば“3”)を入力して[Enter]キーを押すと、
3 * 10 = 30
といった感じで、入力した値の10倍の値が表示されます。

VC++ 2005では、セキュリティを強化したscanf_sなどの関数を使うことが推奨されています。scanf関数を使った場合、「警告1 warning C4996: 'scanf' が古い形式として宣言されました」という警告が表示されます。が、この段階では気にしなくて構いません
リスト1a:入力した値の10倍を表示するプログラム(標準的なCのソース)
#include 

int	main(void)
{
	int num;

	printf("Input number : ");
	scanf("%d", &num);
	printf("%d * 10 = %d\n", num, num * 10);
	return (0);
}

リスト1b:入力した値の10倍を表示するプログラム(VC++のコンソールアプリケーション用ソース)
#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])
{
	int num;

	printf("Input number : ");
	scanf("%d", &num);
	printf("%d * 10 = %d\n", num, num * 10);
	return (0);
}

ソースの入力からコンパイルまで

先に紹介したようなソースコードがエディタによって入力され、ソースファイルができあがります。Cの場合、基本のソースファイルは拡張子が.cとなります。VC++ではC++のソースを示す.cppとなりますが、本質的な部分は変わりません。

ソースコードは、ご存じのように人間に理解できる『人間の言葉』を基本としたプログラムの素です。例えば、printfやnumなどの関数名や変数名は人間にだけ理解できるもので、それらはOSにとってはただのテキスト、CPUにとっては2進数の羅列に過ぎません。

次に、完成したソースコードをコンパイラが読み込んで中間言語のファイルを生成します。Cでは一般に拡張子.objのファイルとなります。

生成された中間言語ファイルは、リンカによってスタートアップ・ルーチンやライブラリ内の関数(の中間言語モジュール)などと結合されます。

大規模なプログラムではソースファイルも役割別に複数のモジュールに分割されるため、その場合はソースから生成された複数の中間言語ファイルも結合されます。

コンパイルからリンクまで

リンカの扱うライブラリとは、複数の中間言語ファイルを1つのファイルにまとめたもので、代表的なものはprintf関数など基本的な入出力処理をまとめた標準入出力関数のライブラリです。その他にも、文字列処理関係や数学関係、グラフィックス関係などなど、機能別のライブラリが多数存在します。

この機能別・役割別のライブラリ関数を利用するために、ソースコードの先頭ではライブラリ内の関数を宣言するヘッダファイル(拡張子.h)が必要になるのです。先のリスト1で先頭に記述されている
#include <stdio.h>
の1行は、前回紹介したように標準入出力ライブラリに収められている関数群を扱うための宣言や定義が記述された“stdio.h”をソースに取り込む指示(プリプロセッサ指令)です。


コンパイラ・ドライバ

ここまでに紹介した一連の処理は、コンパイラやリンカという個別のプログラムを別々に実行しなくても、処理系に用意されたコンパイラ・ドライバというプログラムによって自動的に処理できます。

コンパイラ・ドライバはコンパイラとリンカを自動的に連続実行するプログラムで、単独または少数のソースファイルから実行形式ファイルを生成する場合に役立ちます。LINUXなどUNIX系OSではgcc、LSI-CではLCC.EXE、WindowsのVisual C++ではcl.exeがコンパイラ・ドライバです。

これらの使い方は、この記事の後段で説明します。

VC++のコマンドラインツール

VC++に、こういったコマンドラインでソースをコンパイルできるツールが用意されていることを、はじめて知った方もいるのではないでしょうか? VC++の統合環境でコマンドライン版のプログラムを作れることは前回紹介しましたが、コマンドライン版のcl.exeを使ってソース入力からコンパイル~リンクまですべての工程をコマンドライン上で実行することも可能になっています。

また、VCBUILD.exeというVC++のソリューションやプロジェクトを――ということはWindowsアプリケーションを、コマンドラインでビルドするツールも用意されています。

Windowsアプリケーションは統合環境内でビルドすればいいんじゃないの? と、思う方は多いでしょう。GUIを使用した統合環境でのビルドとコマンドラインからのビルドの決定的な違いは、処理速度です。

僅かな時間差がバカにならない

統合環境でプロジェクトをビルドすると、その進行状況やエラーメッセージが統合環境内のペイン(小さなウィンドウ)に表示されます。実質的なコンパイルやリンクの処理時間には大差なくても、統合環境ではこういったユーザーインターフェイス関連の処理にリソースが費やされ、僅かな時間差となって現れるのです。

この問題は、シンプルで短いソースなら気になりませんが、多数のソースを組み合わせたマルチモジュール開発で大規模なアプリケーションを作る場合、塵も積もれば……ということで、僅かな処理の連続が大きな時間差となって顕著に現れてきます。

コンピュータの処理能力も向上しているため、この処理時間の差は大きくても数十秒程度でしょう。が、何度も『エラー→修正→再ビルド』を繰り返していると、バカにならない時間の浪費となります。

このコラムでは、そういった大規模でコンパイルやリンクに何十秒もかかるようなプログラムは取り上げません。ですから、VC++の統合環境でコンソールアプリケーションのプロジェクトをビルドしても、一向に問題はありません。