この文章は,龍谷大学理工学部数理情報学科「プログラミングおよび実習II」の履修者を対象に高橋が作成しました.C言語のソースを複数のファイルに分割してコンパイルするお話です.
だんだん長いプログラムを書くようになってくると,
という気になってくるかもしれません.「ソースを複数のファイルに分けて作っておき,あとで合体させて実行形式を作る」という方法をとることで,このような欲求を満たすことができます.
↓のソースを例に説明します.
[ prog.c ] 1: #include <stdio.h> 2: 3: int hoge(int x) 4: { 5: return x + 1; 6: } 7: 8: int main() 9: { 10: printf("%d\n", hoge(1)); 11: return 0; 12: }
普通にこのソースをコンパイルするには,
$ cc prog.c -o prog
とすればいいですね(ソースをprog.c,コンパイルしてできる実行形式の名前を progとしています).
このソースは,main()を含む部分と関数hoge()を含む部分に分けることができます.それぞれ main.c ,hoge.c という名前にしてみましょう.
[ main.c ] 1: #include <stdio.h> 2: 3: int hoge(int x); 4: 5: int main() 6: { 7: printf("%d\n", hoge(1)); 8: return 0; 9: }
[ hoge.c ] 1: int hoge(int x) 2: { 3: return x + 1; 4: }
一見すると上のソースを二つに分けただけのようですが、実は main.c の3行目に1行付け加わっています.この行の役割については,後述します.ソースを二つに分けると,別のプログラムにも hoge.c を使ったりできて便利です.では,このようにソースが複数のファイルに別れている場合,どうやってコンパイルしたらよいでしょうか?
上記のソースのコンパイルの方法には,二通りあります.一つは,
$ cc main.c hoge.c -o prog
とする,というものです.ソースが一つのファイルになっている場合とほとんど同じで,単に main.c と hoge.c を並べて書いただけですね. もう一つは,
$ cc -c main.c $ cc -c hoge.c $ cc main.o hoge.o -o prog
というふうに,コンパイルの手順も分割してやる方法です.こちらには,何やら見慣れないオプションやファイル名( "-c" や main.o, hoge.o )が登場しています.
実は,われわれが普段コンパイルと呼んでいる作業は,以下のように「(狭い意味の)コンパイル」と,「リンク」という二つの段階に分かれているのです.
一つ目の方法のように一回の作業で実行形式まで作るやり方では,この二つの段階をまとめて行っています(実はコンパイラが裏でこっそりオブジェクトファイルを作ってあとで消しています).それに対して,二つ目のコンパイルの方法は,
$ cc -c main.c <--- main.c をコンパイル.オブジェクトファイル main.o ができる $ cc -c hoge.c <--- -c は「コンパイルだけやれ」というオプション. hoge.o ができる $ cc main.o hoge.o -o prog <--- main.o, hoge.o などをリンクして prog という実行形式を作る
というようにコンパイルとリンクのそれぞれを逐一実行しています.
一度でできる作業をわざわざ二段階にわけるのは無駄に思えるかもしれませんが,上記のようにいったんオブジェクトファイルを作る方法には,「たくさんのソースの一部だけ修正したときの作業が楽になる」という利点があります.コンパイル&リンクを一度にやる方法では,たとえ hoge.c だけに修正を加えたときでも,両方コンパイルせねばなりません.一方,二段階に分けている場合,main.c のコンパイルは省略できますから,hoge.c をコンパイルして,できた hoge.o と前からある main.o とをリンクするだけでよいのです.ソースが何十,何百ものファイルに分かれているときには,これはとてもありがたいことです(そういう場合,cc ... を手で打つのも面倒やから計算機にまかせたなるわけで,それをやってくれるのが make です(Docs/make犬への道)).
ソースを分けてみるに書いたように,一つのソースを複数に分割する際には,ソースをちょこっといじる必要があります. main.c の3行目,
3: int hoge(int x);
というところがそれです.これは,プロトタイプ宣言と呼ばれるもので,関数 hoge() がどんな関数であるか(何を引数にとるか,返り値は何型か)をコンパイラに知らせるためにあります.ソースを分けた場合,main.c のコンパイル時にはコンパイラは hoge.c のことは何も知りませんから,「こんな関数使うで」と教えてやらねばならない,ということです.
以下のようなプログラムを動かしてみると,プロトタイプ宣言を忘れるとどうなるかわかるかもしれません.
[ main2.c ] 1: #include <stdio.h> 2: 3: int hoge(int x); 4: double hogehoge(double x); 5: 6: int main() 7: { 8: printf("%d\n", hoge(1)); 9: printf("%f\n", hogehoge(1.0)); 10: return 0; 11: }
[ hogehoge.c ] 1: double hogehoge(double x) 2: { 3: return x + 0.1; 4: }
main2.cは,hoge.c と hogehoge.c の二つの関数を使っています. 3行目と4行目のプロトタイプ宣言をコメントにして,コンパイル&リンクして実行してみると,どうなるでしょう.変な結果になりますね.なんでこんなことになるかは,高橋に聞いてください.