分割コンパイル手法の解説

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言語も紹介しています。

関連情報