分割コンパイル

分割コンパイルとは?
分割コンパイルの目的

1つのファイルでプログラムを構成する

例)ATMでの操作

作業1)
bankというプロジェクトを作成し,以下のようなbank.cというソースファイルを作成し,ビルドし,実行して,動作を確認せよ.

ソリューション・エクスプローラー構成


bank.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

/* 個人情報構造体の宣言 */
struct private_info {
	int amount; // 残高
	int password; // パスワード 
};
/* 個人情報構造体の外部変数の定義 */
struct private_info info = { 10000, 1234 };

/* 関数プロトタイプ (bank_common.h) */
void insert_card(void);
void enter_password(void);
void show_amount(void);

/* 関数プロトタイプ (bank_operation.h) */
void deposit(void);
void withdraw(void);
void check(void);

/* メイン関数 */
int
main(void) {
	char buff[2];

	printf("ご利用内容を選択してください [ 預入:d, 引出:w, 照会:s, 終了:f ]\n>>");
	while (fgets(buff, sizeof(buff), stdin) != NULL) {
		if (strcmp(buff, "d") == 0) {
			deposit(); // 預入
		}
		else if (strcmp(buff, "w") == 0) {
			withdraw(); // 引出
		}
		else if (strcmp(buff, "s") == 0) {
			check(); // 照会
		}
		else if (strcmp(buff, "f") == 0) {
			break;
		}
		printf("ご利用内容を選択してください [ 預入:d, 引出:w, 照会:s, 終了:f ]\n>>");
		rewind(stdin);
	}
	return EXIT_SUCCESS;
}

/*****************
 * bank_common.c
 *****************/

/* カード挿入関数 */
void
insert_card(void) {
	printf("カードが挿入されました\n");
}

/* パスワード入力関数 */
void
enter_password(void) {
	char pass[5];

	printf("パスワードを入力してださい\n>>");
	rewind(stdin);
	while (fgets(pass, sizeof(pass), stdin) != NULL) {
		if (info.password != atoi(pass)) {
			printf("パスワードが間違っています.もう一度入力してください\n>>");
			rewind(stdin);
		}
		else {
			break;
		}
	}
	printf("認証に成功しました\n");
}

/* 残高表示関数 */
void
show_amount(void) {
	printf("現在の残高は %d 円です\n", info.amount);
}

/********************
 * bank_operation.c
 ********************/

/* 預入関数 */
void
deposit(void) {
	char buff[128];
	int dep;

	insert_card(); // カード挿入

	printf("預ける金額を入力してください\n>>");
	rewind(stdin);
	while (fgets(buff, sizeof(buff), stdin) != NULL) {
		dep = atoi(buff);
		if (dep < 0) {
			printf("不正な金額です.再度,預ける金額を入力してください\n>>");
		}
		else {
			info.amount += dep;
			break;
		}
	}

	show_amount(); // 残高表示
}

/* 引出関数 */
void
withdraw(void) {
	char buff[128];
	int dep;

	insert_card(); // カード挿入
	enter_password(); // パスワード入力

	printf("引き出す金額を入力してください\n>>");
	rewind(stdin);
	while (fgets(buff, sizeof(buff), stdin) != NULL) {
		dep = atoi(buff);
		if ((dep < 0) || (dep > info.amount)) {
			printf("不正な金額です.再度,引き出す金額を入力してください\n>>");
		}
		else {
			info.amount -= dep;
			break;
		}
	}
	show_amount(); // 残高表示
}

/* 残高照会関数 */
void
check(void) {
	insert_card(); // カード挿入
	enter_password(); // パスワード入力
	show_amount(); // 残高表示
}

複数のファイルでプログラムを構成する

ヘッダファイルの使い方

作業2)
ソースファイルフォルダ内のbank.cを下記のように変更する.
ソースファイルフォルダ内にbank_common.cを,ヘッダーファイルフォルダ内にbank_common.hを下記の内容で新たに作成し,(ビルドではなく)リビルドする.

ソリューション・エクスプローラー構成


bank.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "bank_common.h"

/* 関数プロトタイプ (bank_operation.h) */
void deposit(void);
void withdraw(void);
void check(void);

/* メイン関数 */
int
main(void) {
    // 変更なし
}

/********************
 * bank_operation.c
 ********************/

/* 預入関数 */
void
deposit(void) {
    // 変更なし
}

/* 引出関数 */
void
withdraw(void) {
    // 変更なし
}

/* 残高照会関数 */
void
check(void) {
   // 変更なし
}

bank_common.h

#include <stdio.h>
#include <stdlib.h>

/* 個人情報構造体の宣言 */
struct private_info {
    int amount; // 残高
    int password; // パスワード 
};

/* 個人情報構造体の外部変数の定義 */
struct private_info info = {10000, 1234};

/* 関数プロトタイプ (bank_common.h) */
void insert_card(void);
void enter_password(void);
void show_amount(void);

bank_common.c

#include "bank_common.h"

/*****************
 * bank_common.c
 *****************/

/* カード挿入関数 */
void
insert_card(void) {
    // 変更なし
}

/* パスワード入力関数 */
void
enter_password(void) {
    // 変更なし
}

/* 残高表示関数 */
void
show_amount(void) {
    // 変更なし
}
以下のリンクエラーが発生する
1>------ すべてのリビルド開始: プロジェクト: bank, 構成: Debug Win32 ------
1> bank.c
1> bank_common.c
1> コードを生成中...
1>bank.obj : error LNK2005: _info は既に bank.obj で定義されています。
1>C:\hoge\Debug\bank.exe : fatal error LNK1169: 1 つ以上の複数回定義されているシンボルが見つかりました。
1>
1>ビルドに失敗しました。
1>
1>経過時間 00:00:00.81
========== すべてリビルド: 0 正常終了、1 失敗、0 スキップ ==========

エラーの理由
bank.cがコンパイルされるときに,bank_common.hが呼ばれ, struct private_info infoが定義される
bank_common.cがコンパイルされるときにも bank_common.hが呼ばれ,struct private_info infoが定義される
すなわち,bank.cbank_common.c単独ではそれぞれコンパイルが通る
しかしながら,このbank.objbank_common.objがリンクされる時に,定義の衝突が起こる
対策
ヘッダファイルには外部変数の定義を書いてはいけない.
ヘッダファイル内には外部変数の宣言のみを行い,その定義はソースファイルで行う.

作業3)
作業2)はリビルドに失敗するので,以下のようにプログラムを修正し,再度リビルドする.

bank.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "bank_common.h"

/* 個人情報構造体の外部変数の定義 */
struct private_info info = {10000, 1234};

/* 関数プロトタイプ (bank_operation.h) */
void deposit(void);
void withdraw(void);
void check(void);

/* メイン関数 */
int
main(void) {
    // 変更なし
}

/********************
 * bank_operation.c
 ********************/

/* 預入関数 */
void
deposit(void) {
    // 変更なし
}

/* 引出関数 */
void
withdraw(void) {
    // 変更なし
}

/* 残高照会関数 */
void
check(void) {
   // 変更なし
}

bank_common.h

#include <stdio.h>
#include <stdlib.h>

/* 個人情報構造体の宣言 */
struct private_info {
    int amount; // 残高
    int password; // パスワード 
};

/* 個人情報構造体の外部変数の宣言 */
extern struct private_info info;

/* 関数プロトタイプ (bank_common.h) */
void insert_card(void);
void enter_password(void);
void show_amount(void);
外部変数の宣言でexternをつけなかったらどうなるのか?
struct private_info info仮定義となる(ANSI C規格).
この仮定義はbank_common.cのコンパイル時に,infoの初期化を伴う定義が存在しない時,初期化されていない領域が確保され,リンク時にbank.cで定義されてるstruct private_info infoとリンクされる.
この状況では問題なく動作する.
もし,bank_common.hstruct private_info infoと仮定義して,bank.cinfoを定義しなくても,コンパイル,リンクも通ってしまう.
しかし,infoは初期化されていないので,アプリケーションとして予期せぬ結果を招く.
従って,bank_common.hではstruct private_info infoは別のソースファイルで定義されることを期待している設計になっているので,明示的にexternをつけるべきである.
もし,bank_common.hextern struct private_info infoと宣言し,bank.cinfoを定義しなければ,
1>------ すべてのリビルド開始: プロジェクト: bank, 構成: Debug Win32 -----
1> bank.c
1> bank_common.c
1> コードを生成中...
1>bank.obj : error LNK2001: 外部シンボル "_info" は未解決です。
1>bank_common.obj : error LNK2001: 外部シンボル "_info" は未解決です。
1>C:\hoge\Debug\bank.exe : fatal error LNK1120: 1 件の未解決の外部参照
1>
1>ビルドに失敗しました。
1>
1>経過時間 00:00:01.04
========== すべてリビルド: 0 正常終了、1 失敗、0 スキップ ==========
のようなリンクエラーが発生し,未定義を未然に回避できる.

記憶クラスの復習

局所変数と広域変数
記憶クラス

main.c

#include <stdlib.h>
#include "sub_A.h"
#include "sub_B.h"

int main(void) {
    func_A(10, 20);
    func_B(30, 40);
    return EXIT_SUCCESS;
}

sub_A.h

#include <stdio.h>

void func_A(int x, int y);

sub_A.c

#include "sub_A.h"

static int add(int x, int y) {
    return x + y;
}

void func_A(int x, int y) {
    printf("%d + %d = %d\n", x, y, add(x, y));
}

sub_B.h

#include <stdio.h>

void func_B(int x, int y);

sub_B.c

#include "sub_B.h"

static int add(int x, int y) {
    return x + y;
}

void func_B(int x, int y) {
    printf("%d + %d = %d\n", x, y, add(x, y));
}

多重include回避

作業4)
bank.cを下記のように変更する.
ソースファイルフォルダ内にbank_operation.cを,ヘッダーファイルフォルダ内にbank_operation.hを下記の内容で新たに作成し,リビルドする.

ソリューション・エクスプローラー構成


bank.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "bank_common.h"
#include "bank_operation.h"

/* 個人情報構造体の外部変数の定義 */
struct private_info info = {10000, 1234};

/* メイン関数 */
int
main(void) {
    // 変更なし
}

bank_operation.h

#include "bank_common.h"

/* 関数プロトタイプ (bank_operation.h) */
void deposit(void);
void withdraw(void);
void check(void);

bank_operation.c

#include "bank_operation.h"

/********************
 * bank_operation.c
 ********************/

/* 預入関数 */
void
deposit(void) {
    // 変更なし
}

/* 引出関数 */
void
withdraw(void) {
    // 変更なし
}

/* 残高照会関数 */
void
check(void) {
   // 変更なし
}
以下のコンパイルエラーが発生する
1>------ すべてのリビルド開始: プロジェクト: bank, 構成: Debug Win32 ------
1> bank_operation.c
1> bank_common.c
1> bank.c
1>c:\hoge\bank_common.h(8): error C2011: 'private_info' : 'struct' 型の再定義
1> c:\hoge\bank_common.h(8) : 'private_info' の宣言を確認してください。
1> bank_common.c
1> コードを生成中...
1>
1>ビルトに失敗しました。
1>
1>経過時間 00:00:00:57
========== すべてリビルド: 0 正常終了、1 失敗、0 スキップ ==========

エラーの理由
bank_operation.cbank_common.cのコンパイルは問題ない.
しかし,bank.cのコンパイルの際に,構造体private_infoの再定義でコンパイルエラーになっている.
bank.cのコンパイルの際,まず,bank_common.hincludeされ,構造体private_infoが宣言される(ここまでは問題ない).
次に,bank_operation.hincludeされ,bank_operation.hの中で,再度,bank_common.hincludeされる(bank_operation.hでも構造体private_infoが必要なのでincludeしなければならない).
すると,すでに宣言済みの構造体private_infoがまた宣言されることとなり,コンパイルエラーとなる.
対策
条件付コンパイルを用いることで多重includeを回避する.

作業5)
作業4)はリビルドに失敗するので,以下の様にプログラムを修正し,再度リビルドする.

bank_common.h

#ifndef BANK_COMMON_H
#define BANK_COMMON_H

#include <stdio.h>
#include <stdlib.h>

/* 個人情報構造体の宣言 */
struct private_info {
    int amount; // 残高
    int password; // パスワード 
};

/* 個人情報構造体の外部変数の宣言 */
extern struct private_info info;

/* 関数プロトタイプ (bank_common.h) */
void insert_card(void);
void enter_password(void);
void show_amount(void);

#endif

bank_operation.h

#ifndef BANK_OPERATION_H
#define BANK_OPERATION_H

#include "bank_common.h"

/* 関数プロトタイプ (bank_operation.h) */
void deposit(void);
void withdraw(void);
void check(void);

#endif
VC++では#pragma onceで同様のことが可能であるが移植性が下がる->最近では様々なコンパイラでサポートされてきている.

練習問題)
下記のmain.cとgoods.c(いずれも変更不可)を適切に動作させるためのgoods.hを作成せよ.

goods.h


goods.c

#include "goods.h"

void registration(struct goods* item, char* name, int value) {
    strcpy_s(item->name, sizeof(item->name), name);
    item->value = value;
}

void print(struct goods item) {
    printf("品名:%s\n", item.name);
    printf("価格:%d\n", item.value);
}

main.c

#include <stdlib.h>
#include "goods.h"

int main(void) {
    int i;
    struct goods item[3];

    registration(&item[0], "book", 1000);
    registration(&item[1], "DVD", 3000);
    registration(&item[2], "pen", 100);

    for (i = 0; i < 3; ++i) {
        print(item[i]);
    }

    return EXIT_SUCCESS;
}