分割コンパイル

プログラムの規模が大きくなると、ソースファイルを複数に分割して記述するのが普通だ。
というわけで、試しに昨日の btreesort を分割してみた。ソートを実装する btree.c と main() 関数のある main.c だ。
まずは main.c。

#include
#include
#include

#define MAX 10

void PrintArray(int ary[]);
void BTreeSort(int ary[], const int n);

int main(void)
{
    int i;
    int ary[MAX];

    srand((unsigned)time(NULL));

    for (i = 0; i < MAX; i++) {
        ary[i] = rand() % 100;
    }
    printf("unsorted: ");
    PrintArray(ary);
    BTreeSort(ary, MAX);
    printf("sorted: ");
    PrintArray(ary);

    return 0;
}

void PrintArray(int ary[])
{
    int i;

    for (i = 0; i < MAX; i++) {
        printf("%2d ", ary[i]);
     }
    printf("\n");
}

10行目に注目。BTreeSort() の関数プロトタイプを記述して、このファイルの中で使えるようにしてある。 で、その BTreeSort() 本体は btree.c に記述する。

#include

typedef struct tree {
    int value;
    struct tree *left;
    struct tree *right;
} Tree;

void BTreeSort(int ary[], const int n);
Tree *Insert(Tree *root, int val);
int Traverse(Tree *root, int ary[], int n);
void FreeTree(Tree *root);

void BTreeSort(int ary[], const int n)
{
    int i;
    Tree *root = NULL;

    for (i = 0; i < n; i++) {
        root = Insert(root, ary[i]);
    }
    Traverse(root, ary, 0);
    FreeTree(root);
}

Tree * Insert(Tree *root, int val)
{
    if (root == NULL) {
        root = (Tree *)malloc(sizeof(Tree));
        root->value = val;
        root->left = NULL;
        root->right = NULL;
    } else {
        if (val < root->value) {
            root->left = Insert(root->left, val);
        } else {
            root->right = Insert(root->right, val);
        }
    }

    return root;
}

int Traverse(Tree *root, int ary[], int n)
{
    if (root->left != NULL) {
        n = Traverse(root->left, ary, n);
    }
    ary[n++] = root->value;
    if (root->right != NULL) {
        n = Traverse(root->right, ary, n);
    }

    return n;
}

void FreeTree(Tree *root)
{
    if (root == NULL) {
        return;
    } else {
        FreeTree(root->left);
        FreeTree(root->right);
        free(root);
        return;
    }
}

さて、分割コンパイルだ。
今まではソースファイルが1つだったので、ひとつの gcc コマンドでいっぺんに実行ファイルを作っていた。実際には、gcc は *.c ファイルから *.o ファイルを作り、*.o ファイルから実行ファイルを作る。*.o ファイルをオブジェクトファイルとよび、オブジェクトファイルを作ることをコンパイル、オブジェクトファイルから実行ファイルを作ることをリンクという。リンクは複数のオブジェクトファイルのほか、標準ライブラリなどもリンクする。
gcc では -c オプションをつけることで、上に書いたコンパイル、つまり実行ファイルじゃなくて *.o ファイルを作ることができる。複数のソースファイルに分けた場合、ひとつずつコンパイルすること(オブジェクトファイルを作ること)を分割コンパイルという。
オブジェクトファイルは実行ファイルではないので、最終的に実行ファイルを作るには、リンクする必要がある。じゃあなぜ分割コンパイルするのかといえば、例えばあるソースファイルだけ修正した場合、それだけコンパイルしなおせば、あとはコンパイル済みのオブジェクトファイルとリンクすればいい。つまりコンパイルの時間(と手間)が省力化できるってわけだ。

やってみよう。まずは main.c から。

takatoh@nightschool $ ls
btree.c  main.c
takatoh@nightschool $ gcc -Wall main.c -c
takatoh@nightschool $ ls
btree.c  main.c  main.o

オブジェクトファイル main.o が出来た(警告も出ていない)。
つぎ、btree.c。

takatoh@nightschool $ gcc -Wall btree.c -c
takatoh@nightschool $ ls
btree.c  btree.o  main.c  main.o

こちらもオブジェクトファイルが出来た。
これら2つのオブジェクトファイルをリンクするには次のようにする。

takatoh@nightschool $ gcc main.o btree.o -o btreesort
takatoh@nightschool $ ls -l
合計 28
-rw-rw-r-- 1 takatoh takatoh 1384  6月 27 08:01 btree.c
-rw-rw-r-- 1 takatoh takatoh 2360  6月 27 13:48 btree.o
-rwxrwxr-x 1 takatoh takatoh 9056  6月 27 13:49 btreesort
-rw-rw-r-- 1 takatoh takatoh  614  6月 27 08:00 main.c
-rw-rw-r-- 1 takatoh takatoh 2248  6月 27 13:47 main.o

これで実行ファイル btreesort が出来た。
うまく動くか試してみよう。

takatoh@nightschool $ ./btreesort
unsorted: 59 12 88 98 83 78  6 38 88 55 
sorted:    6 12 38 55 59 78 83 88 88 98

OK。大丈夫みたい。