K3の住民

最近レア社のことをほとんど書いていない自称レア社好きがゲームやプログラミングについて色々書いていく。

【プログラミング】コンパイルにおけるよくある勘違いを解説

皆さん、こんにちは。雪圀です。
今回は多くの方が勘違いしていると思われる、コンパイル(某会社のことでは無い)
について解説します。


例えば、多くのCコンパイラは"コンパイル"をすると、実行ファイルが作られます。

本当はこのような言い方をしてはいけません。何故なら、コンパイルは、Cファイル
から実行ファイルを作るのでは無く、Cファイルをプリプロセスしてコンパイル可能
になったファイルを、アセンブリファイルに変換することを示すからです。

では、どうすればCファイルから実行ファイルが作られるのかについて、解説して
いこうと思います

Cファイルから実行ファイルまでの簡単な流れ

先ず、Cファイルから実行ファイルまでの簡単な流れは以下の通りです。


Cファイル

プリプロセス済みCファイル

アセンブリファイル

オブジェクトファイル

実行ファイル


なんと、Cファイルから実行ファイルにするまで、このような手順が必要なのです。

プリプロセス

Cファイルに前処理を行うことを、プリプロセスと言います。行う前処理は、ヘッダ
ファイルのincludeや、マクロの展開、コメントの消去等です。

このプリプロセスを行う機能をプリプロセッサと呼びます。

コンパイル

前処理を行ったCファイルを、アセンブリファイルに変換します。
アセンブリ言語は、機械語を人間でも読めるように変換したものです。コンピュータ
C言語を読むことが出来ないので、それをアセンブリ言語というグローバル言語に
翻訳する、という感じで考えていただければ良いかなと思います。

アセンブル

しかし、やはりコンピュータはアセンブリ言語をそのまま読むことが出来ません。
それを機械語に直したオブジェクトファイルにすることを、アセンブルと言います。

このアセンブリファイルをアセンブルする機能はアセンブラと言います。

リンク

オブジェクトファイルの統合を行い、実行ファイルにします。1つの場合は、すぐに
実行ファイルにします。

プログラムによってはCファイルを2つ使う場合(メインルーチンとサブルーチンで
分けた方がソースコードとして分かりやすい場合)もあるので、その統合をこの
リンクで行います。
したがって、Cのプログラマになるうえでアセンブリファイルは知らなくとも、
オブジェクトファイルの存在は知っておいた方が良いでしょう。

因みにこのオブジェクトファイルをリンクする機能をリンカと言います。

GCCで実際に流れを確認してみよう

GCCは、先ほど書いた機能全てが備わっており、それにより全てのファイルを作る
ことが可能です。

先ず、プリプロセスを行うには、以下のコマンドを実行します。

gcc -E [cファイル名]

但し、これではプリプロセスを行うだけでファイルは作られないので、ファイルを
作る場合は、

gcc -E [cファイル名] > [前処理済みcファイル名]

以下のようにリダイレクトを利用します。

次にコンパイルを行う場合、以下のコマンドを実行します。

gcc -S [ファイル名]

これ以降は前の段階を行う前のファイルでも可能なのでファイル名とさせていただき
ます。
これによりアセンブリファイルを作ることが出来ます。

アセンブルをする場合、以下のコマンドを実行します。

gcc -c [ファイル名]

これにより、アセンブリ言語機械語に変換され、オブジェクトファイルが生成され
ます。

最後に、リンクをして、実行ファイルを作ります。

gcc -o [実行ファイル名] [ファイル名]

実行ファイル名が無い場合はa.outもしくはaという実行ファイルが生成されます。

2つのプログラムを実行ファイルにする

2つのCファイルを実行ファイルにする方法を紹介しましょう。
例として、以下の仕様のプログラムを作ります。

input.c output.c
scanf()を使って整数を標準入力 整数の偶数/奇数を判定
printf()を使って判定結果を標準出力

先ずはinput.cです。

/* input.c */

#include <stdio.h>

// 整数の値を入力する関数(valueは参照渡し用)
void input(int *value)
{
	int a;				// 入力用
	
	printf("値を入力してください: ");
	scanf("%d", &a);		// 値の入力
	
	*value = a;			// 参照渡し用変数に格納
}

次にoutput.cです。

/* output.c */

#include <stdio.h>

// input()関数の外部参照
extern int input(int*);

// main()関数
int main(void)
{
	int value = 0;			// 入力する値
	
	input(&value);			// 値の入力
	
	printf("入力した値は");
	
	// 偶数/奇数の判定
	if( value % 2 == 0 ) {		// 偶数
		printf("偶数です\n");
	} else {			// 奇数
		printf("奇数です\n");
	}
	
	return 0;
}

関数の外部参照を行うにはただ単にプロトタイプ宣言を行うのでは無く、extern
修飾子を入れてプロトタイプ宣言する必要があります。これが無いとエラーを吐き
ます。違うファイルなので当然っちゃ当然ですが。

最後に2つのオブジェクトファイルを作り、リンクしましょう。

gcc -c input.c output.c
gcc -o io input.o output.o

これで2つのCファイルを使った実行ファイルが生成出来ます。

まとめ

以上のことで、知っておくべきなのは、以下の3つです。


以上です。余談ですが、僕もこれを初めて知ったとき「え、僕の思ってるコンパイル
って実はコンパイルじゃなかったの!?」って思いました。

因みにそれを知っている人でも今回解説したコンパイルは狭義のコンパイルで、
広義的な意味では合ってるよって人もいますが、僕はそれは違うと思っています。
それによって現に混乱している人がいるのだから、間違ったことは正すべきであると
思います。

今回はこれくらいにしときます。