情報基礎演習II − 第10回


1.記憶クラス


ブロックの先頭で宣言された変数は、 そのブロックの始まりで生成(記憶領域の割り当て)され、 そのブロックの終りで消去(記憶領域の開放)されます。

従って、例えば同じ関数を2回呼び出したとき、 1回目の呼び出しのときの変数の値は、 2回目の呼び出しのときまで保存されません。 例えば、次のプログラムでは、 sub1 の1回目の呼び出しで変数 i に 10 が代入されるものの、 2回目の呼び出しのときには i に 10 以外のものが入っています。

#include <stdio.h>

void sub1(int f)
{
    int i; /* sub1 の中だけで有効な変数(自動変数) */

    if (f == 0) {
      i = 10;
    }
    else {
      printf("i = %d\n", i);
    }
}

void sub2(void)
{
    int x = 20;
}

int main(void)
{
    sub1(0);   /* sub1 の i に 10 を入れる */
    sub2();
    sub1(1);   /* sub1 の i を表示する */
               /* i が保存されていれば 10 が表示されるはず */
    return 0;
}
このような変数を自動変数と呼びます。 自動変数の宣言は通常次のように行います。
auto int i;
auto は自動変数の宣言であることを示します。 auto を省略した場合、 ブロックの内側で宣言された変数は自動変数であると見なされます。 普通 auto は省略されます。

ブロック内で変数された変数が ブロックの終了後も保存される用にするには、 static を付けて変数を宣言します。

static int i;
このような変数を静的変数と呼びます。 静的変数の記憶領域の割り当ては、 プログラムの実行開始時に行われ、 プログラムが終了するまで保存されます。 上のプログラムにおいて sub1 の変数 i を static int i; と 宣言すれば、2回目の呼び出しのときにも i には 10 が入っています。

■課題44■


2.変数のスコープ


ブロックの先頭で宣言した変数は、 そのブロックの中でのみ有効です。 下の例では、関数 main 内の変数 i と、 関数 sub 内の変数 i は、 別の変数になります。 従って関数 main から sub 内の i を参照することは、 引数などを用いない限りできません。
#include <stdio.h>

void sub(void)
{
    int i;      /* 関数 sub の中だけで有効 */

    for (i = 0; i < 10; i++) {
        printf("%3d", i);
    }
    printf("\n");
}

int main(void)
{
    int i;      /* 関数 main の中だけで有効 */

    for (i = 0; i < 10; i++) {
        sub();
    }

    return 0;
}
上の例で、 for によって繰り返されている { ... } の部分も ひとつのブロックです。 従って、この部分でも変数宣言を行うことができます。
#include <stdio.h>

int main(void)
{                                       <---------------+
    int i;                                              |
                                                        |
    for (i = 0; i < 10; i++) {          <-----------+   |
        int j;                                      |   |
                                                    |   |
        for (j = 0; j < 10; j++) {      <-------+   |   |
            printf("%3d", j);                  (1) (2) (3)
        }                               <-------+   |   |
        printf("\n");                               |   |
    }                                   <-----------+   |
                                                        |
    return 0;                                           |
}                                       <---------------+
上の例では、(2) のブロックで宣言されている変数 j は その内側のブロック (1) でも有効ですが、 ブロック (2) の外側では使えません。

このような変数の有効範囲のことを スコープ(可視範囲) といいます。

■課題45■

■課題46■


外部変数

複数の関数の間で同じ変数を共有したいときは、 その変数を関数の外側の、 その変数を共有しようとする全ての関数より前で宣言します。
#include <stdio.h>

int sum; /* 共有しようとする変数 */

void goukei(int n)
{
    do {
      sum += n; /* 共有しようとする変数 */
    }
    while (--n > 0);
}

void hyouji(void)
{
    printf("合計は %d\n", sum); /* 共有しようとする変数 */
}

int main(void)
{
    sum = 0; /* 共有しようとする変数 */
    goukei(100);
    hyouji();

    return 0;
}
このような変数は外部変数と呼ばれます。 外部変数の記憶領域はプログラムの実行開始時に割り当てられます。 すなわち外部変数は静的変数です。

外部変数の宣言が、 その変数を参照している部分より前にないときは、 その部分より前で 「どこかにそう言う変数があるよ」という宣言をします。

extern int i;
この宣言自体は i という変数に対してメモリを割り当てませんが、 どこか他のところで i が宣言されていることを示します。

■課題47■


3.分割コンパイル


ソースプログラムは複数のファイルに分けて作ることができます。
goukei.c
extern int sum; /* どこか他で宣言されている */

void goukei(int n)
{
    do {
      sum += n;
    }
    while (--n > 0);
}

hyouji.c
#include <stdio.h>

extern int sum; /* どこか他で宣言されている */

void hyouji(void)
{
    printf("合計は %d\n", sum);
}

main.c
int sum; /* 共有しようとする変数の宣言 */

int main(void)
{
    sum = 0;
    goukei(100);
    hyouji();

    return 0;
}

このようにファイルを分けた場合は、 次のようにしてコンパイルします。
cc main.c goukei.c hyouji.c -o prog
これによって main.c goukei.c hyouji.c をそれぞれコンパイルし、 作成されたオブジェクトファイルを結合して prog という実行ファイルを 作成します。

この例では、外部変数 sum (の実体)は main.c の中だけで宣言されており、 goukei.c hyouji.c では、

extern int sum;
によって、それぞれのソースプログラムの中以外に sum が用意されていることを宣言しています。

一方、関数の外部で、

static int sum;
のように宣言した場合には、 この変数はそのソースファイルの それが宣言されている場所より後ろ以外からは参照されないことを示します。 例えば、上記の例の main.c において sum を次のように宣言すると、 sum が他から参照できなくなるため、 goukei.c および hyouji.c にある extern int sum; の実体がないことになり、 リンク時にエラーになります。
main.c
static int sum; /* このファイル内だけで使用される変数の宣言 */

int main(void)
{
    sum = 0;
    goukei(100);
    hyouji();

    return 0;
}

■課題48■


関数プロトタイプ

課題48において、kadai48b.c に含まれる関数 inc を kadai48a.c の関数 goukei 内で使用するために、 kadai48a.c の先頭で次のように宣言していました。
extern int inc(void);
これまで例では、 関数は必ずそれを呼び出している部分より前で定義していました。 これは外部変数の宣言と同じ規則です。

従って、 関数の定義が同じソースファイルの関数を呼び出している部分より前に無い場合は、 外部変数同様その関数を extern を使って宣言してやる必要があります。 この場合、戻り値の型の他に、 引数の型も宣言します (関数の中身は必要ありません)。 これを関数プロとタイプと言います。

#include <stdio.h>

extern void swap(int *, int *); /* 関数プロトタイプ */

int main(void)
{
    int x, y;

    x = 10;
    y = 20;
    swap(&x, &y);
    printf("x=%d, y=%d\n", x, y);

    return 0;
}

void swap(int *a, int *b)
{
    int t;

    t = *a;
    *a = *b;
    *b = t;
}
この例では関数 swap の戻り値の型が void(値を返さない)で、 2つの引数がいずれも int 型のポインタであることを示しています。
extern void swap(int *, int *);