プログラミング演習2(課題3、第1回)

分割コンパイル

分割コンパイルとは?

分割コンパイルの目的

単一のファイルでプログラムを作成する


例)銀行での操作



作業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, 1111};

/* 関数プロトタイプ */
void insertCard();
void enterPassword();
void showAmount();
void deposit();
void withdraw();
void check();

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

    printf("ご利用内容を選択してください [ 預入:d, 引出:w, 照会:s, 終了:f ]\n>>");
    while (fgets(buff, sizeof (buff), stdin) != NULL) {
        fflush(stdin);
        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>>");
    }
    return (EXIT_SUCCESS);
}

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

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

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

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

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

    insertCard(); // カード挿入

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

    showAmount(); // 残高表示
}

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

    insertCard(); // カード挿入
    enterPassword(); // パスワード入力

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

/* 残高照会関数 */
void
check() {
    insertCard(); // カード挿入
    enterPassword(); // パスワード入力
    showAmount(); // 残高表示
}
ソリューションエクスプローラ内

複数のファイルで構成する


ヘッダファイルの使い方

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

ソースファイル名(bank.c)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "bank_common.h"

void deposit();
void withdraw();
void check();

int
main(void) {
/*変更なし*/
}

void
deposit() {
/*変更なし*/
}

void
withdraw() {
/*変更なし*/
}

void
check() {
/*変更なし*/
}
ヘッダファイル名(bank_common.h)
#include <stdio.h>
#include <stdlib.h>

struct private_info{
	int amount;
	int password;
};
struct private_info info={10000,1111};

void insertCard();
void enterPassword();
void showAmount();
ソースファイル名(bank_common.c)
#include "bank_common.h"

void
insertCard() {
/*変更なし*/
}

void
enterPassword() {
/*変更なし*/
}

void
showAmount() {
/*変更なし*/
}
ソリューションエクスプローラ内

作業3) 作業2)はリビルドに失敗(infoは既にbank.objで定義されているというエラー)するので、以下の様にプログラムを修正し、再度リビルドする。

ソースファイル名(bank.c)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "bank_common.h"
struct private_info info={10000,1111};

void deposit();
void withdraw();
void check();

int
main(void) {
/*変更なし*/
}

void
deposit() {
/*変更なし*/
}

void
withdraw() {
/*変更なし*/
}

void
check() {
/*変更なし*/
}
ヘッダファイル名(bank_common.h)
#include <stdio.h>
#include <stdlib.h>

struct private_info {
	int amount;
	int password;
};
extern struct private_info info;

extern void insertCard();
extern void enterPassword();
extern void showAmount();

エラーの理由

bank.cがコンパイルされるときに、bank_common.hが呼ばれ、 struct private_info infoが定義される。bank_common.cがコンパイルされるときにも bank_common.hが呼ばれ、struct private_info infoが定義される。 すなわち、bank.c,bank_common.c単独ではそれぞれコンパイルが通る。 しかしながら、このbank.objとbank_common.objがリンクされる時に、定義の衝突が起こる。

対策

ヘッダファイルには外部変数の定義を書くべきではない。ヘッダファイル内には 外部変数の宣言のみを行い、その定義は別で行う。

記憶クラス

  1. 局所変数(ローカル変数):関数内で定義され、その関数のみで参照できる。他の関数からは参照できない
  2. 広域変数(グローバル変数):関数外で定義され、どの関数からも参照できる。
void function(void);
int a;
int main(void) {
	int b;
} void function(void) {
	int c;
	int b;
	int a;
}
  1. 自動変数(auto)
  2. 外部変数(extern)
  3. 静的変数(static)
ソースファイル名(main.c)
#include "subA.h"
#include "subB.h"

int main(void) {
	funcA(10,20);
	funcB(30,40);
}
ヘッダファイル名(subA.h)
#include <stdio.h>

void funcA(int x,int y);
ヘッダファイル名(subB.h)
#include <stdio.h>

void funcB(int x,int y);
ソースファイル名(subA.c)
#include "subA.h"

static int add(int x,int y) {
	return (x+y);
}
void funcA(int x,int y) {
	printf("%d + %d = %d\n",x,y,add(x,y));
}
ソースファイル名(subB.c)
#include "subB.h"

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

シナリオと問題点

AさんがsubA.h, subA.cをBさんがsubB.h, subB.cを開発している。 Aさん、Bさんはあまり打合せをせずに、開発を進めてしまっている。 2人とも、個人的にaddという関数を勝手に作って利用したい。

対策

個人的に利用したい関数、変数はstaticをつけて、他のモジュールから参照できないようにする。 (staticがついていないと、関数名の衝突が発生する)

多重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,1111};

int
main(void) {
/*変更なし*/
}
ヘッダファイル名(bank_operation.h)
#include <stdio.h>
#include <stdlib.h>
#include "bank_common.h" /* depositなどの関数内でinsertCardなどの関数を使用しているのでincludeしなければならない */

extern void deposit();
extern void withdraw();
extern void check();
ソースファイル名(bank_operation.c)
#include "bank_operation.h"

void deposit() {
/*変更なし*/
}

void withdraw() {
/*変更なし*/
}

void check() {
/*変更なし*/
}
ソリューションエクスプローラ内

作業5) 作業4)はリビルドに失敗(private_infoがの再定義エラー)するので、以下の様にプログラムを修正し、再度リビルドする。

ヘッダファイル名(bank_common.h)
#ifndef BANK_COMMON_H
#define BANK_COMMON_H
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

struct private_info { 
	int amount;
	int password;
};
extern struct private_info info;

extern void insertCard();
extern void enterPassword();
extern void showAmount();
#endif
ヘッダファイル名(bank_operation.h)
#ifndef BANK_OPERATION_H
#define BANK_OPERATION_H
#include <stdio.h>
#include <stdlib.h>
#include "bank_common.h"

extern void deposit();
extern void withdraw();
extern void check();
#endif

エラーの理由

bank.cがコンパイルされる際に、まずbank_common.hがincludeされ、 struct private_infoが定義される。 次に、bank_operation.hがincludeされる。 この時、bank_operation.hはbank_common.hをincludeしているので、また、struct private_infoが 定義され、同じ構造体が2回定義されることになり、コンパイルエラーとなる。

対策

条件付コンパイルを用いることで多重includeを回避する。
VC++では#pragma onceで同様のことが可能であるが移植性が下がる

練習問題

問題) 下記のmain.cとsub.c(いずれも変更不可)を動作させるためのsub.hを作成せよ。 (strcpyに関するコンパイルエラーは無視しても良い。 但しstring.hが必要)

ソースファイル(main.c)
#include "sub.h"

int
main(void) {
	struct student member;
	init(&member,"宇大 太郎",20);
	print(member);
}
ソースファイル(sub.c)
#include "sub.h"

void
init(struct student *member,char *name,int age) {
	strcpy(member->name,name);
	member->age  = age;
}

void
print(struct student member) {
	printf("名前:%s\n",member.name);
	printf("年齢:%d\n",member.age);
}
ヘッダファイル(sub.h)