分割コンパイル手法の解説
C言語の分割コンパイル手法を解説します。コンパイラにはgccを使用します。実務で十分に使える分割コンパイル手法です。分割コンパイル手法とは、ここでは、アプリケーションのソースファイル、ライブラリのヘッダファイル、ライブラリのソースファイルに分けて記述し、それらをコンパイルし、リンクして実行ファイルを生成する手法のことをいいます。
分割コンパイル手法の概要
分割コンパイル手法の概要について、解説します。
アプリケーションのソースファイル
アプリケーションのソースファイルとはmain関数が含まれるソースファイルのことです。アプリケーションは、ここから実行されます。
アプリケーションのソースファイルを、コンパイルするためには、アプリケーションで利用されている関数や構造体が、インクルードしたヘッダに存在する必要があります。
アプリケーションのソースファイルのコンパイルを行うときには、関数や構造体などの名前の解決だけが行われればよく、他のライブラリのソースファイルを知る必要はありません。リンクする場合には、ライブラリのソースファイルをコンパイルし、オブジェクトファイルになったものが、必要になります。
ライブラリのヘッダファイル
自作のライブラリのヘッダファイルです。構造体定義、typedef文、列挙型、定数マクロ、関数マクロ、関数プロトタイプ宣言は、ヘッダファイルで行います。
アプリケーションのソースファイル、または、ライブラリのソースファイルの中で、インクルードされます。
ライブラリのソースファイル
自作のライブラリのソースファイルです。ライブラリのソースファイルの中では、関数定義を行います。関数のプロトタイプ宣言は、ヘッダファイルで行います。
ライブラリのソースファイルの中では、ライブラリのヘッダファイルをインクルードします。
ライブラリのソースファイルを、コンパイルするためには、ライブラリのソースファイルで利用されている関数や構造体が、インクルードしたヘッダに存在する必要があります。
ライブラリのソースファイルのコンパイルを行うときには、関数や構造体などの名前の解決だけが行われればよく、他のライブラリのソースファイルを知る必要はありません。リンクする場合には、ライブラリのソースファイルをコンパイルし、オブジェクトファイルになったものが、必要になります。
分割コンパイルの手順
分割コンパイルの手順です。
1.ライブラリのソースファイルをコンパイルして、オブジェクトファイルを生成する。すべての、ライブラリのソースファイルで行う。
2.アプリケーションのソースファイルをコンパイルして、オブジェクトファイルを生成する。
3.上記で生成したすべてのオブジェクトファイルをリンクして、実行ファイルを生成する。
ヘッダファイルは「include」というディレクトリの中に格納することにします。アプリケーションのソースファイルとライブラリのソースファイルは「src」というディレクトリの中に格納することにします。ヘッダファイルでは、インクルードガードを行います。
分割コンパイルのサンプル
分割コンパイルのサンプルです。
アプリケーションのソースファイル
アプリケーションのソースファイル「myapp.c」です。
myapp.c
#include <stdio.h> #include "mylib1.h" #include "mylib2.h" int main(void) { mylib1_print(); mylib2_print(); }
ライブラリのヘッダファイル
ライブラリのヘッダファイル「mylib1.h」と「mylib2.h」です。
mylib1.h
#ifndef MYLIB1_H #define MYLIB1_H void mylib1_print (void); #endif
mylib2.h
#ifndef MYLIB2_H #define MYLIB2_H void mylib2_print (void); #endif
ライブラリのソースファイル
ライブラリのソースファイル「mylib1.c」と「mylib2.c」です。
mylib1.c
#include <stdio.h> #include "mylib1.h" void mylib1_print (void) { printf("mylib1_print\n"); }
mylib2.c
#include <stdio.h> #include "mylib2.h" void mylib2_print (void) { printf("mylib2_print\n"); }
コンパイル
アプリケーションのソースファイルと、ライブラリファイルを、それぞれコンパイルします。ヘッダファイルの場所をオプション「-I」で指定します。
gcc -Iinclude -c -o myapp.o src/myapp.c gcc -Iinclude -c -o mylib1.o src/mylib1.c gcc -Iinclude -c -o mylib2.o src/mylib2.c
コンパイルされるとオブジェクトファイルになります。オブジェクトファイルは機械語になっていますが、まだ実行ファイルではありません。
リンク - 実行ファイルの作成
リンクして実行ファイルを作成します。関数名などのシンボルが、実体に結びつけられます。
gcc -o myapp myapp.o mylib1.o mylib2.o
実行
実行ファイルを実行してみましょう。
./myapp
出力結果です。
mylib1_print mylib2_print
これで、C言語の分割コンパイル手法をマスターできました。ライブラリファイルが増えても同じです。
今回の例では、ヘッダには関数のプロトタイプ宣言、ソースファイルには関数定義しか行っていませんが、大事なことは、ヘッダに何を定義すべきかを、きっちり理解することです。
ヘッダファイルでは、構造体定義、typedef文、列挙型、定数マクロ、関数マクロ、関数のプロトタイプ宣言を行います。
ソースファイルでは、関数定義を行います。
型の循環参照を避ける方法
上記の分割コンパイル方法で、ひとつ問題になるのは、型の循環参照が発生してしまうということです。ライブラリが相互参照することは、実務のプログラミングで、普通に起こりうることですが、これを解決する手段に、型宣言ヘッダという手法があります。ヘッダファイル間の読み込みを減らし、循環参照を解決できます。
オブジェクト指向C言語
分割コンパイルの応用としてオブジェクト指向C言語も紹介しています。