Linux講座にようこそ。このページは「C言語プログラミング入門 - 第10章.データの有効範囲と寿命を規定する記憶クラス」です。

C言語プログラミング入門

10. データの有効範囲と寿命を規定する記憶クラス(1/1)

10.1 記憶クラスとは

関数間のデータのやりとりは引数と返り値で行えますが、これらにはちょっとした欠点があります。関数間でやりとりするデータが多い場合、引数の数が多くなりますが、そうするとコーディングが面倒になりますし、分かりにくいプログラムになりがちです。また、返り値で返せるデータは1個のみです。

記憶クラスとは
【図10-1】記憶クラスとは

そこで、もっと簡単にデータのやりとりができるように、変数や配列を関数間で共有することができます。つまり、1つの変数の値を複数の関数で参照や更新ができるということです。また、逆に、変数や配列を特定の関数でのみ使えるようにすることもできます。これらを指定するのが記憶クラスと呼ぶものです。

変数名や関数名などの名前を識別子と呼んでいますが、記憶クラスは識別子の有効範囲有効期間を指定します。

有効範囲
識別子のソースプログラム上における使用可能な範囲です。
有効期間
識別子が存在している期間(寿命)です。

記憶クラスは識別子の宣言時に指定します。宣言の方法については後ほど説明します。

10.1.1 有効範囲

有効範囲
【図10-2】有効範囲

有効範囲には内部変数(ローカル変数)と外部変数(グローバル変数)の2種類があります。

内部変数は関数内やブロック内で宣言し、それらの中だけで使用可能です。従って、使用範囲を限定できますので、他の関数で誤って変数の値を変更してしまったというような不良を防ぐことができます。今までの例題プログラムで使っていた変数や配列は全て内部変数です。

一方、外部変数は関数の外側で宣言し、複数の関数で参照・更新が可能です。従って、関数間でデータ共有が簡単に行えます。しかし、このことは誤って変数の値を更新してしまったというような不良が発生しがちですし、不良箇所を見つけ出すのも大変です。

このようなことから、基本的には内部変数を使い、どうしても必要な場合のみ外部変数を使うようにした方がよいでしょう。

10.1.2 有効期間

有効期間
【図10-3】有効期間

有効期間には自動変数静的変数の2種類があります。

自動変数は宣言時にメモリ上に確保され、使用を終了するとメモリ上から破棄されてしまいます。使用終了はブロックや関数の実行が終了した時点です。破棄されたメモリはリサイクルされますので、メモリを有効に使うという点では優れています。今までの例題プログラムで使っていた変数や配列は全て自動変数です。

一方、静的変数はプログラム起動時にメモリ上に確保され、プログラム実行中は存続します。また、静的変数は宣言する箇所により関数内静的変数関数外静的変数の2種類があります。

関数内静的変数は関数の内側で宣言し、有効範囲は関数内のみになります。一方、関数外静的変数は関数の外側で宣言し、有効範囲は宣言した行からソースファイルの最後までになります。

10.2 宣言の形式

10.2.1 形式

変数や配列および、関数の宣言形式についてはすでに説明しましたが、これらの宣言に記憶クラスを指定することができます。

※ 変数の宣言
記憶クラス指定子 型名 変数名;

※ 配列の宣言
記憶クラス指定子 型名 配列名[要素数];

※ 関数プロトタイプ宣言
記憶クラス指定子 返り値の型名 関数名(引数の型名1 引数名1, 引数の型名2 引数名2, …);
記憶クラス指定子
記憶クラスとしてはautostaticextern、register、typedefを指定できますが、ここではauto、static、externについて説明し、typedefについては「12. その他の型(3/4)」で説明します。

10.2.2 内部変数(ローカル変数)

今までは変数の宣言を関数の先頭で行っていましたが、ブロックの先頭で行うこともできます。もっとも、関数も一つのブロックですので、関数の先頭ということはブロックの先頭ということもできます。今までのように、ブロックの先頭で宣言したものは内部変数です。内部変数の有効範囲はブロック内です。

また、初期値を指定しなかった場合の値は不定になりますので注意してください。

int main(void)
{
    int    v1; ← (1)
    char   v2; ← (1)
    /* func関数のプロトタイプ宣言 */
    int func(int p_v1, char p_v2); ← (2)

    v1 = func(10, v2); ← (3)

}

/* func関数の宣言 */
int func(int arg1, char arg2) ← (4)
{
    int    v3; ← (5)
    char   v2; ← (5)


    for(int idx = 0; idx <= arg1; ++idx) ← (6)
    {


    }

    if(v3 <= 10)
    {
        int    v4 = 0; ← (7)


    }
}
(1)
main関数の内側で宣言していますので内部変数です。有効範囲はmain関数内のみです。初期値の指定がありませんので値は不定です。
(2)
func関数の関数プロトタイプ宣言ですが、これもmain関数の内側で行っていますので、有効範囲はmain関数内のみです。別の関数でfunc関数を呼び出す場合は、そちらの関数でも関数プロトタイプ宣言が必要です。
(3)
func関数の呼び出しです。
(4)
func関数の定義ですが、ここで宣言している仮引数(arg1とarg2)は内部変数扱いとなります。
(5)
func関数の内部変数です。変数v2は同じ名前でmain関数でも宣言していますが、ブロックが異なるため別のものになります。
(6)
int idx = 0;はfor文のブロックの内側で宣言をしていることになります。従って、変数idxの有効範囲はfor文のブロック内だけです。
(7)
変数v4はif文のブロックの内側で宣言をしているため内部変数になります。有効範囲はif文のブロック内だけです。初期値の指定がありますので値は0です。

10.2.3 外部変数(グローバル変数)

関数の外側で宣言すると外部変数になります。外部変数の有効範囲はソースファイル上の宣言した行からソースファイルの最後までになります。

また、初期値を指定しなかった場合の値はになります。(整数型の場合は0、実数型の場合は0.0、文字型の場合はヌル文字('\0')になります)

int    v1 = 0; ← (1)

int func1(int p_v1, char p_v2); ← (2)
int func2(void); ← (2)

int main(void)
{
    int     v1; ← (3)
    int     v2;
    char    v3;

    v1 = func1(v2, v3); ← (4)

    v1 = func2(); ← (5)
}

int func1(int arg1, char arg2) ← (6)
{

    v1 = func2(); ← (7)

}

int func2(void) ← (8)
{


}
(1)
関数の外側で宣言していますので外部変数です。有効範囲はこの行以降です。
(2)
このソースファイル内で呼び出す関数の関数プロトタイプ宣言です。有効範囲はこの行以降です。
(3)
外部変数v1と同じ名前ですが、関数の内側で宣言していますので、こちらは内部変数です。main関数内ではこちらの変数が有効になります。
(4)
func1関数の呼び出しです。
(5)
func2関数の呼び出しです。
(6)
func1関数の定義です。
(7)
func2関数の呼び出しです。関数プロトタイプ宣言は関数の外側でかつ、この行以前に行っていますので、呼び出すことができます。また、ここで使っている変数v1は(1)で宣言している外部変数です。
(8)
func2関数の定義です。

外部変数を別のソースファイルに定義した関数でも有効にしたい場合は、そちらのソースファイルで記憶クラス指定子のexternを指定して宣言します。externを指定した変数は外部変数として確保してあることをコンパイラに伝えるだけで、メモリ上に領域を確保しません。上記例題の外部変数v1を別のソースファイルでも使いたい場合は次のように指定します。

extern int    v1;

int func3(void)
{
    int     v4;

    v4 = v1 + 10; ← 変数v1は外部変数です

}

10.2.4 自動変数

ブロックの先頭で、記憶クラス指定子のautoまたは、記憶クラス指定子を指定しない場合は自動変数になります。autoは互換性維持のためにあるようなので通常は指定しませんので、結局、ブロックの先頭で、記憶クラス指定子を指定しないということになります。つまり、宣言は内部変数と同じになりますので、内部変数と自動変数は同じものを着目する性質により呼び分けているだけです。

自動変数はブロックの実行が終了すると、メモリ上の領域は解放されてリサイクルされてしまいます。

int main(void)
{
    auto int     v1;
    int          v2;

}

10.2.5 静的変数

記憶クラス指定子のstaticを指定すると静的変数になります。静的変数はプログラム開始時にメモリ上に確保され、プログラム実行中は存続します。

静的変数は宣言する箇所により、関数内静的変数関数外静的変数の2種類があります。関数内静的変数はブロックの先頭で宣言しますので、有効範囲はブロック内のみです。それに対して関数外静的変数は関数の外側で宣言しますので、有効範囲は宣言した行からソースファイルの最後までになります。

外部変数と関数外静的変数は有効範囲が似ていますが、異なる点があります。外部変数は記憶クラス指定子のexternを指定すれば、別のソースファイルでも使うことができますが、関数外静的変数は使うことができません。

static int v1; ← 関数外静的変数です
static int func1(int p_v1, char p_v2);
   ↑ 関数内静的変数として宣言していますので、このソースファイル以外では呼び出せません
int main(void)
{
    int     v2;

    v1 = func1(v2, 'a');

}

static int func1(int arg1, char arg2)
{
    static int     v3 = 0; ← 関数内静的変数です
    int            v4 = 0; ← 自動変数です

    v3 = arg1 + v4; ← この間数が終了しても、代入された値は保持されます

}

10.2.6 例題

半角文字列を逆順にして出力します。StrRev関数は引数で指定した文字列を逆順にした配列を返します。(入力文字は半角で100文字以下とし、スペースやタブ等の空白文字は含みません)

  1. #include <stdio.h>
  2. #include <string.h>
  3. int main(void)
  4. {
  5.     char    a_buff[4096];
  6.     char    *p_rev;
  7.     int     return_code;
  8.     /* StrRev関数のプロトタイプ宣言 */
  9.     char *StrRev(char *Rev);
  10.  
  11.     printf("文字列を入力してください ==> ");
  12.     scanf("%s", a_buff);
  13.     if(strlen(a_buff) >= 1 && strlen(a_buff) <= 100)
  14.     {
  15.         p_rev = StrRev(a_buff);
  16.         printf("逆順:%s\n", p_rev);
  17.         return_code = 0;
  18.     }
  19.     else
  20.     {
  21.         return_code = 1;
  22.     }
  23.  
  24.     return return_code;
  25. }
  26.  
  27. /* 引数の文字列を逆順にして返す */
  28. char *StrRev(char *pRev)
  29. {
  30.     static char     a_rev[101];
  31.     char            *p_cur;
  32.     char            *p_rev;
  33.  
  34.     /* 文字列の最後の文字を探す */
  35.     for(p_cur = pRev; *p_cur; ++p_cur)
  36.         ;
  37.     --p_cur;
  38.     /* 逆順にコピー */
  39.     for(p_rev = a_rev; p_cur >= pRev; ++p_rev, --p_cur)
  40.     {
  41.         *p_rev = *p_cur;
  42.     }
  43.     *p_rev = '\0';
  44.  
  45.     return a_rev;
  46. }
$ ./ex10_1.prg
文字列を入力してください ==> Hello!!.
逆順:.!!olleH
$
2行目
13行目でライブラリ関数のstrlenを使っていますので、このヘッダーファイルが必要です。strlen関数は配列中の文字数を返します。(ヌル文字は含みません)
9行目
StrRev関数のプロトタイプ宣言です。返り値はchar型のポインタ(char型の配列の先頭アドレス)つまり、文字列です。
13行目
入力した文字列の文字数をライブラリ関数のstrlen関数でチェックします。
15行目
StrRev関数を呼び出して、入力した文字列の逆順文字列を返り値として取得します。返り値は変数p_revに代入しておき、次の行で使っています。
28行目
StrRev関数の定義です。返り値はchar型のポインタです。
30行目
この配列(a_rev)に逆順文字列を作り、返り値として返します。返り値として返した後(この間数の実行終了後)も、この配列は使われますので、メモリ上に存在しなければならないため、静的変数(関数内静的変数)にします。
45行目
逆順文字列を返り値として返します。