プログラミング演習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) リビルドに失敗(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をつけて,他のモジュールから参照できない ようにする.

多重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) リビルドに失敗(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で同様のことが可能であるが移植性が下がる

練習問題

練習問題1)下記のプログラムが動作するようにsub.cを修正せよ.(main.c,sub.hは修正不可)

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

int a=10;
int b=20;

int
main(void){
	printf("%d + %d = %d\n",a,b,add());
}
ヘッダファイル(sub.h)
extern int a;

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

int add(){
	return (a+b);
}

練習問題2)下記の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)
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)