[[takataka]] | [[Docs]]

#contents


*はじめに [#v3745a97]

**これは何? [#re15248e]

この文章は,龍谷大学理工学部数理情報学科「[[プログラミングおよび実習II]]」の履修者を対象に高橋が作成しました.C言語のソースを複数のファイルに分割してコンパイルするお話です.

**更新履歴 [#y6442d31]

-2003-03-11 [[HTML版>http://tortoise1.math.ryukoku.ac.jp/~takataka/cpro/doc/sepcomp.html]]最終更新
-2006-10-22 wiki化,追記したり修正したり
-2008-04-02 [[Docs]]の下へ移動

*ソースを分けたい [#qed93329]

だんだん長いプログラムを書くようになってくると,

-一つのばかでかいソースファイルにだらだら書くのは鬱陶しいから何とかしたい.
-よく使うものを「部品」として用意しといて,いろんなプログラムで使い回したい. 

という気になってくるかもしれません.「ソースを複数のファイルに分けて作っておき,あとで合体させて実行形式を作る」という方法をとることで,このような欲求を満たすことができます.

↓のソースを例に説明します.

#pre{{
[ 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としています).

*ソースを分けてみる [#he06a8d2]

このソースは,main()を含む部分と関数hoge()を含む部分に分けることができます.それぞれ main.c ,hoge.c という名前にしてみましょう.

#pre{{
[ 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:   }
}}    

#pre{{
[ hoge.c ]

 1:   int hoge(int x)
 2:   {
 3:      return x + 1;
 4:   }
}}

一見すると上のソースを二つに分けただけのようですが、実は main.c の3行目に1行付け加わっています.この行の役割については,後述します.ソースを二つに分けると,別のプログラムにも hoge.c を使ったりできて便利です.では,このようにソースが複数のファイルに別れている場合,どうやってコンパイルしたらよいでしょうか?

*コンパイルとリンク [#y228abf8]

上記のソースのコンパイルの方法には,二通りあります.一つは,

 $ cc main.c hoge.c -o prog

とする,というものです.ソースが一つのファイルになっている場合とほとんど同じで,単に main.c と hoge.c を並べて書いただけですね. もう一つは,

#pre{{
$ cc -c main.c
$ cc -c hoge.c
$ cc main.o hoge.o -o prog
}}    

というふうに,コンパイルの手順も分割してやる方法です.こちらには,何やら見慣れないオプションやファイル名( "-c" や main.o, hoge.o )が登場しています.

実は,われわれが普段コンパイルと呼んでいる作業は,以下のように「(狭い意味の)コンパイル」と,「リンク」という二つの段階に分かれているのです.

-コンパイル: 個々のソースを機械語の命令に翻訳して,オブジェクトファイルと呼ぶものを作る
-リンク: 複数のオブジェクトファイルをくっつけたりして実行可能なプログラムを作る 

一つ目の方法のように一回の作業で実行形式まで作るやり方では,この二つの段階をまとめて行っています(実はコンパイラが裏でこっそりオブジェクトファイルを作ってあとで消しています).それに対して,二つ目のコンパイルの方法は,

#pre{{
$ 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犬への道]])).

*プロトタイプ宣言 [#z0559282]

ソースを分けてみるに書いたように,一つのソースを複数に分割する際には,ソースをちょこっといじる必要があります. main.c の3行目,

 3:   int hoge(int x);

というところがそれです.これは,プロトタイプ宣言と呼ばれるもので,関数 hoge() がどんな関数であるか(何を引数にとるか,返り値は何型か)をコンパイラに知らせるためにあります.ソースを分けた場合,main.c のコンパイル時にはコンパイラは hoge.c のことは何も知りませんから,「こんな関数使うで」と教えてやらねばならない,ということです.

以下のようなプログラムを動かしてみると,プロトタイプ宣言を忘れるとどうなるかわかるかもしれません.

#pre{{
[ 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:   }
}}

#pre{{
[ hogehoge.c ]

 1:   double hogehoge(double x)
 2:   {
 3:      return x + 0.1;
 4:   }
}}

main2.cは,hoge.c と hogehoge.c の二つの関数を使っています. 3行目と4行目のプロトタイプ宣言をコメントにして,コンパイル&リンクして実行してみると,どうなるでしょう.変な結果になりますね.なんでこんなことになるかは,高橋に聞いてください.

トップ   編集 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS