Shammer's Philosophy

My private adversaria

分割コンパイル

C言語で作成したソースを gccコンパイルする際、何もオプションを指定しなければ a.out という名前の実行ファイルが生成される。しかし、実行ファイルができるまでにもいくつかの工程が存在している。

  1. プリプロセッサの処理
  2. オブジェクトコードの作成
  3. オブジェクトコードとライブラリのリンク

最終的に実行されるのは main 関数なので、main 関数がない状態のファイル(呼び出されることを想定した関数のみのソースコードなど)をオプションなしでコンパイルするとエラーになる。以下の hello-sub-1.c というファイルを

#include <stdio.h>

void func1(){
    printf("Hello from sub 1.\n");
}

gcc でオプションなしでコンパイルすると、

undefined reference to `main'

というエラーになる。でも、-c オプションをつけてやれば問題なくコンパイルされ、hello-sub-1.o というファイルが生成される。さらに、この拡張子 .o のファイルを複数つなげてコンパイルすると実行ファイルができる。ただ、指定したファイルの中に main 関数を含むものがないとダメ。上記の hello-sub-1.c の他に、hello-sub-2.c というファイルと main.c というファイルを作成してみた。

#include <stdio.h>

void func2(){
    printf("Hello from sub 2.\n");
}
#include <stdio.h>

int main(void){
    func1();
    func2();
    return 0;
}

これらを以下の順番でコンパイルすると、a.out というファイルが作成され実行できる。

  1. gcc -c hello-sub-1.c
  2. gcc -c hello-sub-2.c
  3. gcc -c main.c
  4. gcc hello-sub-1.o hello-sub-2.o main.o

main.c で hello-sub-1.c や hello-sub-2.c を include してしまうと関数の二重定義でエラーになる。これまでの感覚だと、main.c に include がないのになぜ gcc -c main.c は問題なくコンパイルされるのか、というように思ってしまうが、ソースコードを単純にオブジェクトコードにするだけなら func1 や func2 の所在がわからなくても問題はない。func1 や func2 がどこにあるか、というのが必要になるのはリンクのタイミングだということになる。リンク完了してできたファイル、a.out は問題なく実行できる。

 $ ls
hello-sub-1.o  hello-sub-2.o    main.c  hello-sub-1.c  hello-sub-2.c  main.o
$ gcc hello-sub-1.o hello-sub-2.o main.o
$ ls
hello-sub-1.c  hello-sub-2.c  main.o  a.out  hello-sub-1.o  hello-sub-2.o  main.c
$ ./a.out 
Hello from sub 1.
Hello from sub 2.
$

ここで、hello-sub-1.c を少し修正。

#include <stdio.h>

void func1(){
    printf("Hello from sub 1!!!\n");
}

次に、gcc -c hello-sub-1.c を実行し、続いて gcc hello-sub-1.o hello-sub-2.o main.o 後に a.out を実行。

$ gcc -c hello-sub-1.c
$ gcc hello-sub-1.o hello-sub-2.o main.o
$ ./a.out
Hello from sub 1!!!
Hello from sub 2.
$

main.o や hello-sub-2.o をコンパイルし直すことなく、hello-sub-1.c の変更を反映できた。もちろん、main.c に include を書いて gcc main.c としてもコンパイルはできるのだが、この場合は変更されていない hello-sub-2.c もコンパイルされる。つまり、不要なコンパイルが発生する。コンパイルされるファイルが一つや二つというレベルであればよいが、これが数十数百という状態になると、この不要なコンパイルのコストが馬鹿にならなくなってくる。サンプルを書いている程度であれば問題になることはないだろうが、分割コンパイルは知っておくべきことだと思う。ちょっとだけれども実験することができてよかった。