2009年5月30日

関数、変数のスコープとmakeファイル

今回は、下記のような流れでやっていきますよ。

1.extern宣言,static宣言
2.ヘッダファイル
3.makeファイル作成

ポイントは、関数、変数のスコープmakeファイルです。

1.*.c, *.h作成
mai.c
#include

void init();//init()のプロトタイプ宣言
void func1();//
func1()のプロトタイプ宣言

void init(){}
void func1(){}
int main(void){

init();
func1();
exit(0);

}

main.c内のプロトタイプ宣言は皆さんもご存知の通り、
min()内でinit()やdone()を使用する際にコンパイラに引数の数と型と戻り値の型を
教えてあげるために宣言するものでした。

$gcc -c main.c -Wall
これは、問題なくコンパイルされますね。

では、以下のように別ファイルに関数の実体がある場合はどうでしょうか?

mai.c
#include

void init();//init()のプロトタイプ宣言
void func1();//func1()のプロトタイプ宣言

int main(void){

init();
func1();
exit(0);

}


init.c
void init(){}
func1.c
void func1(){}
$gcc -c main.c -Wall
$gcc -c init.c -Wall
$gcc -c func1.c -Wall
$gcc main.o init.o func1.o dummy -Wall

同様に
問題なくコンパイル・リンクされました。

つまり、init()とfunc1()は外部に公開されている事になりますね。
外部に公開されているというのは、main.c以外の別ファイルから
関数を呼び出す事ができるという事です。
つまり、関数はデフォルトの定義でグローバルになるわけです。
明示的に関数をグローバルに宣言するには、次のようにしてextern接頭語を用います。


mai.c
#include

extern void init();//init()のプロトタイプ宣言
void func1();//func1()のプロトタイプ宣言

int main(void){

init();
func1();
exit(0);

}

$gcc -c main.c -Wall
$gcc main.o init.o func1.o dummy -Wall

結果は、extern接頭語を使用してもしなくても同様ですね。
また、関数の定義自体にも

func1.c
extern void func1(){


とすることもできます。
ちなみにstatic接頭語も勉強しましょう。

static接頭語を用いて関数や変数を定義すると、
スコープはファイル中に限定されます。
つまり、static定義されている関数は、
そのファイルの中でしか使えないという事になります。
そのため、static定義された関数のプロトタイプ宣言を書く場合、
プロトタイプ宣言と実際の関数の定義は同一ファイル内に
ある必要があります。

次に変数の場合です。
1)value1は、関数内でstatic宣言しています。
2)value2は、関数内で普通に宣言しています。
3)value3は、関数外でstatic宣言しています。
4)value4は、関数外で普通に宣言しています。

【func1.c】
#include
#include

static int value3 = 3;
int value4 = 4;


void func1(){

static int value1 = 1;
int value2 = 2;
printf("value1 = %d\n",value1);
printf("value2 = %d\n",value2);

value1 = value1 + 1;
value2 = value2 + 2;

}

void func2(){

printf("value3 = %d\n",value3);
value3 = value3 + 1;

}

【main.c】

#include
#include

extern void init();//init()のプロトタイプ宣言
void func1();
//func1()のプロトタイプ宣言
void func2();//func2()のプロトタイプ宣言

extern int value4;//value4のextern宣言

int main(void ){

init();
func1();
func1();
func2();
printf("value4 = %d\n",value4);
exit(0);

}



$gcc -c main.c -Wall
$gcc -c init.c -Wall
$gcc -c func1.c -Wall
$gcc main.o init.o func1.o dummy -Wall


実行結果
$ ./dummy.exe
value1 = 1
value2 = 2
value1 = 2
value2 = 2
value3 = 3
value3 = 4
value4 = 4

結果をまとめると、
1)関数内でstatic宣言された変数value1は、変数の値が保存されます。
2)関数内で普通にに宣言された変数value2は、func()が呼ばれる度に
初期化されfunc()を抜けると、値は消滅する。
3)関数外でstatic宣言された変数value3は、func.c全体がスコープになり、
変数の値が保存されます。これを広域変数といいます。
4)関数外で普通に宣言された変数value4(main.cでextern宣言されている変数value4)は、
extern宣言する事で、別ファイルにあるvalue4の実体の型をコンパイラに通知することができます。

関数と変数で微妙に違いますね~
ややこしや~~~

所で、上記のようなサンプルプログラムの規模なら問題ないのですが、
大規模のプログラムの場合、関数や変数を利用するたびにプロトタイプ宣言や
extern宣言を行う必要が出てくる事になります。面倒です。
そこで次の章です。

2.ヘッダファイル

下記のように関数のプロトタイプ宣言をヘッダファイルに書きます。

【main.c】
#include
#include

#include "../inc/init.h"
#include"../inc/func.h"

int main(void){

init();
func1();
func1();
func2();
func2();
printf("value4 = %d\n",value4);
exit(0);

}

【init.h】
extern void init();//init()のプロトタイプ宣言

【init.c】
#include "../inc/init.h"

void init(){
}

【func.h】

void func1();//func1()のプロトタイプ宣言
void func2();//func2()のプロトタイプ宣言
extern int value4;//value4のextern宣言

【func.c】
#include
#include "../inc/func.h"

static int value3 = 3;
int value4 = 4;


void func1(){

static int value1 = 1;
int value2 = 2;
printf("value1 = %d\n",value1);
printf("value2 = %d\n",value2);

value1 = value1 + 1;
value2 = value2 + 2;

}

void func2(){

printf("value3 = %d\n",value3);
value3 = value3 + 1;
}

ここで、ヘッダファイルや.cファイルには何を書いたら言いかというと、

ヘッダファイルには
  • 外部に公開する定義、宣言
.cファイルには
  • そのファイル専用の宣言や定義
  • 関数、変数の実体
となります。

今回は、コンパイル・リンクを手動で行ってきました。
面倒です。
決まった手順でコンパイル・リンクするので、
makeファイルを作りましょう。
ってことで、次の章です。

3.makeファイル作成

拡張子はなしで、ファイル名をMakefileとします。
内容は下記の通りとなります。
Makefileは*.cと同じディレクトリにおきます。

OBJS =main.o init.o func.o
TARGET += dummy.exe
CC =gcc
CFLAGS +=-O
CFLAGS +=-wall
#setenv CFLAGS -I/../inc/


.SUFFIXES: .c .o

all: $(TARGET)

$(TARGET):$(OBJS)
         $(CC) $(OBJS) -o $(TARGET) $(CFLAGS)

.c.o: $*.c
   $(CC) -c $*.c $(CFLAGS)

clean:
   rm -f $(OBJS) $(TARGET)

$make とすると、実行ファイルが出来上がります。
$make clean とすると、*.oと*.exeが削除されます。
楽チンですね^^

まず、各種変数の設定を行います。
-
---------------------------------------
OBJS =main.o init.o func.o
TARGET += dummy.exe
CC =gcc
CFLAGS +=-O
CFLAGS +=-wall

---------------------------------------
下記は、*.cから*.oを作成するという宣言らしいです。

.SUFFIXES: .c .o
---------------------------------------
下記は、$make allを実行すると、TARGETが生成されます。
TARGETはdummy.exeです。

all: $(TARGET)

---------------------------------------
下記は、TARGETの生成手順の定義です。

$(TARGET):$(OBJS)
        $(CC) $(OBJS) -o $(TARGET) $(CFLAGS)

---------------------------------------
下記は、*.cから*.oの生成手順の定義です。

.c.o: $*.c
   $(CC) -c $*.c $(CFLAGS)

---------------------------------------
下記は、$make cleanを実行すると、OBJSとTARGETを削除します。
OBJSは*.c、*.oです。
TARGETはdummy.exeです。
clean:
   rm -f $(OBJS) $(TARGET)


あと、「+=」と「?=」の説明です。

「+=」は、前回の変数の値に追加するという意味。
「?=」は、値が設定されていないときのみ、値を設定するという意味。

つまり、デフォルト定義が存在するという事です。
今のところcygwin上では、どこに設定ファイルがあるか不明です。
知ってる方、教えてくださいm(_ _)m

あと、includeのパス設定をmakeファイルに追加は
また、次回にします。

とりあえず、今回は以上です。





















0 件のコメント: