gccとはbash同様FSFのGNUプロジェクトによって開発されたコンパイラでGNU C Compiler、後にC/C++/Objective C/FORTRAN/Java...といった各種プログラミング言語に対応したことからGNU Compiler Collectionとして知られています。
コンパイラとコンパイルではUNIX/Linux及びシェル環境におけるコンパイラとコンパイルについてccとgccの関係、続くUNIX/Linux C コンパイル過程では、実行ファイルができるまでに具体的に何が行われているのかについて、その流れを追いました。
ここではgccのコンパイルによるそれぞれの過程の確認とarコマンド(archive/複数のファイルを時には圧縮してひとまとめにした記録やその保管場所・書庫、archiver/それらを記録するソフトウェアやコマンド)を使用してstatic library/スタティックライブラリ/静的ライブラリを作成、静的ライブラリから実行ファイルの生成をしてみます。
#include <stdio.h>
#include <stdlib.h>
/* foo.c */
int main(void){
printf ("Hello!!\n");
return EXIT_SUCCESS;
}
例えば、こんなCプログラムがあるとします。
ちなみにコンパイル説明のところでほぼ同様のサンプルソースを書きましたが、CはUNIX/Linux同様、正常終了は0ということを印象付ける為に!?returnに0を渡しましたが、(find、grepで検索するのも面倒とは言わずに他にどこにあるのか忘れたことにして)ここではstdlib.hを入れて正常終了の0を表すマクロEXIT_SUCCESSに替えてみました。
$ gcc foo.c
$ ls -F
a.out* foo.c
$ rm a.out
$ ls
foo.c
gccにオプションなしでボディファイルだけを引数として渡した最もシンプルなコンパイルを実行後、ls コマンドに-Fオプション付きで見てみると自動的にa.out*という名前のファイルが生成されていますが、*はls -Fで閲覧できる実行ファイルのマークです(同様にディレクトリの末尾にはスラッシュが付きます)。
それがわかったところで、これからできるファイルを見やすくする為に、このa.out*を削除します。
$ gcc -o foo foo.c
$ ls -F
foo* foo.c
$ rm foo
$ ls -F
foo.c
次にoオプションを付けると任意の名前の実行ファイルを生成できることがわかります。
前述の理由から、これもここではとりあえず削除しておき、次は順次オブジェクトファイルまで生成してみます。
$ gcc -x c -E foo.c > foo.i
$ ls
foo.c foo.i
$ gcc -x c -S foo.c
$ ls
foo.c foo.i foo.s
$ gcc -c foo.c
$ ls
foo.c foo.i foo.o foo.s
ボディファイルから実行ファイルが生成される過程で使用後に削除されてしまう中間ファイルであるプリプロセス済みソースファイル(.i)、アセンブリコードファイル(.s)、オブジェクトファイル(.o)は、それぞれ-E/-S/-cオプション付きでこのようにすれば残しておくことができます。
gcc -Eの結果はファイル生成ではなく標準出力への出力なのでリダイレクトして拡張子.iのファイルを作成しています。
-E/-Sオプションと共に渡している -x c は-xオプションにcという引数を渡しており、このcは拡張子.cつまりボディファイルを表しますが、これはどんなタイプのファイルを生成するかを指定するもので、この例含め複雑なものでなければ省略可能です。
ちなみに-xオプションの引数は、入力ファイルの拡張子が.cならc/.iならcp-output/.sならassembler/.oならobjectです。
$ gcc -o foo foo.o
foo.oが最新(foo.cやfoo.cがincludeしているかもしれないヘッダファイル(.h)などに変更がない)なら、次回からはfoo.oから実行ファイルを生成できるようになります。
そういったファイル管理もソース数が増えるほど煩雑となりますが、幸いコンパイルとソース管理を自動化できるmakeコマンドがあります。
ここまではまだソースファイルが1つなのでわざわざオブジェクトファイルを残すようにボディファイルをコンパイルしてから後でボディファイルから実行ファイルを生成することは手間がかかるだけで何のメリットもないように見えますが、オブジェクトファイルが増えたり、ライブラリを作成、利用したりすると、そのメリットが見えてくると思います。
ソースファイル1つに付き1つ生成されるオブジェクトファイル(群)と静的ライブラリファイル(拡張子.a/archive/アーカイブ)や共有ライブラリファイル(動的ファイル/拡張子.so/shared object/共有オブジェクト)などのライブラリを結びつけて最終的な実行ファイルを生成する過程がコンパイルにおけるリンク処理でgccは、この時リンクするプログラムであるリンカldを呼びます。
$ ls
foo.c bar.c foo.o bar.o
$ ar crsv libfoob.a foo.o bar.o
a - foo.o
a - bar.o
$ ls
foo.c bar.c foo.o bar.o libfoob.a
これらのライブラリも特に静的ライブラリについてはその基本はオブジェクトファイル(群)であり、例えば新規にlibfoob.aという静的ライブラリを生成するには(シンボル解決が不要な為gccやldで行わなくてもよいので)往々にしてcrsvという4つのオプション(厳密にはrコマンドと一般修飾子c,s,v)付きarコマンドを使用します。
libfoob.aのライブラリ名はfoobとして参照できます(例えばlibc.aはC言語標準ライブラリであり、そのライブラリ名はなんとc一文字)。
$ mkdir ../src
$ mkdir ../obj
$ ls -F ..
lib/ obj/ src/
$ mv *.c ../src
$ mv *.o ../obj
$ ls
libfoob.a
$ pwd
/home/hoge/lib
libfoob.aができたのでfoo.c、bar.c、foo.o、bar.oを他のディレクトリに入れておこうと思いsrcやobjディレクトリを作成して移動することにしました。
ふと1つ上の階層を見てみると、今作ったsrc、obj以外にlibが...というわけでsrc、objディレクトリはなかったのに、なぜか、libというディレクトリだけはあったらしいことがわかります。
ということは。。。と作業ディレクトリを確認してみると実はここはhogeのlibディレクトリだったっていうことで完璧!?
$ gcc -Wall -o foob libfoob.a
ところでlibfoob.aから実行ファイルを生成するには、libfoob.aとそのまま入力して指定することもできるのですが、
$ gcc -Wall -o foob -L. -lfoob
l (小文字のエル)オプションを渡してスペースを入れずにライブラリ名を指定することの方が多いようです。
$ ls
foob* libfoob.a
尚、-Wallオプションはgccが推奨する警告を全て表示、-oは実行ファイル名指定、-Lとそれに続く1つのドットはカレントディレクトリをライブラリ検索パスとして追加(デフォルト検索パスとして/usr/lib等以外は含まれずカレントディレクトリは検索されない為)という意味です。
次は共有ライブラリ(動的ライブラリ)を作ってリンクしてみます。