ローカル変数

ローカル変数局所変数: local variable)とは、プログラムの一部分でしか利用できない変数のことである。一般的にグローバル変数(大域変数)と対比される。ローカル変数の定義はプログラミング言語によって異なるので、詳細な説明は言語別の項に譲る。

C言語

C言語およびC++の標準規格の文面では「ローカル変数」という用語は使われていないが、関数内の同じ「ブロック」という領域内からのみ参照可能な変数のことを便宜上ローカル変数と呼ぶことが多い。C99よりも前の規格ではブロックの先頭部分でのみ定義可能だったが、C99からはC++同様に任意の位置で定義可能である。

ある関数fにおけるローカル変数aスコープ (scope) すなわち可視性は、その関数f内において、その変数aが宣言された場所から、その変数aが定義されたブロックを抜けるまでである。スコープ外からは参照することができない。ローカル変数aを定義したブロック内で別の関数gを呼び出すと、関数gからはその変数aが見えなくなるが、メモリ上には依然として存在しており、関数gが終了して関数fに戻り、変数aのスコープに入ると再び見えるようになる。異なる関数(異なるブロック)で同じ名前のローカル変数を定義してもよく、お互いに影響を与えないので、名前の衝突を回避する必要がない。

#include <stdio.h>

void g(void); /* 関数 g の前方宣言 */

void f(void) {
    /* 関数 f のローカル変数 */
    int a, b;
    a = 1;
    b = 2;
    g(); /* 関数 g の呼び出し */
    /* 関数 f のローカル変数は、他の関数内のローカル変数とは別物であり、影響を受けない */
    printf("%d, %d\n", a, b);
}

void g(void) {
    /* 関数 g のローカル変数 */
    int b;
    /* 以下はコンパイルエラーとなる */
    /*a = -1;*/
    /* 他の関数内の同名のローカル変数には影響を与えない */
    b = -2;
}

同じ関数内でも、属するブロックによってローカル変数のスコープ(および寿命)が異なる。下記の例において、2行目で宣言されているローカル変数iのスコープは2行目から6行目の中括弧までであり、4行目で宣言されているローカル変数jのスコープは4行目から5行目の中括弧までである。

void Function(void) {
    int i = 1; /* スコープの広いローカル変数 */
    {
        int j = 1; /* スコープの狭いローカル変数 */
    }
}

C言語のローカル変数の寿命 (lifetime) は、デフォルトではそのローカル変数を定義したブロックを抜けるまでである。ローカル変数は宣言により自動的にメモリ割り当てが行なわれるが、ブロックを抜けて寿命が尽きると自動的にメモリ割り当てが解除される。また、ローカル変数が定義された関数の制御フローが呼び出し元に返り、コールスタック領域が解放されると、そのローカル変数のメモリ領域は自動的に解放される。したがって「自動変数英語版[注釈 1]とも呼ばれる。未初期化の自動変数の内容は不定 (indeterminate) である[1]

ローカル変数宣言にstaticキーワード(静的記憶域クラス指定子)を付加すると、「静的ローカル変数」[注釈 2]となり、変数寿命はプログラムの生存期間と同一となる。C言語では静的ローカル変数はグローバル変数と同じくプログラム開始処理以前に一度だけ初期化されるが、C++では制御フローが静的ローカル変数の定義箇所に到達した際にその初期化式が一度だけ評価され、その値によって変数は一度だけ初期化される[注釈 3]

C99の例を示す。なお、下記(1)はC言語およびC++03規格までのC++ではauto int a = 0;と等価だが、C++11以降はautoキーワードの意味が変更され、型推論の機能に利用されるようになったため、等価ではなくなった[注釈 4]

#include <stdio.h>

int Function1(void) {
    int a = 0; // (1) 通常のローカル変数(自動変数)の宣言と定義。初期化は関数を呼び出すたびに毎回行なわれる。
    a += 1;
    return a;
}

int Function2(void) {
    static int s = 0; // (2) 静的ローカル変数の宣言と定義。初期化は一度だけ行なわれる。
    s += 1;
    return s;
}

int main(void) {
    for (int i = 0; i < 5; ++i) {
        printf("auto(%d) = %d\n", i, Function1());
        printf("static(%d) = %d\n", i, Function2());
    }
    return 0;
}

以下のコードはC++ではコンパイル可能だが、C言語では静的ローカル変数はコンパイル時定数で初期化されなければならないため、コンパイル不可能である。

#include <stdio.h>

int Func(int x) {
    if (x > 0) {
        static int firstPositive = x;
        return firstPositive;
    } else if (x < 0) {
        static int firstNegative = x;
        return firstNegative;
    }
    return 0;
}

int main(void) {
    printf("%d\n", Func(1)); // 1
    printf("%d\n", Func(5)); // 1
    printf("%d\n", Func(-5)); // -5
    printf("%d\n", Func(-1)); // -5
    printf("%d\n", Func(0)); // 0
    return 0;
}

C++ではSingleton パターンの実現に静的ローカル変数が利用されることがある。

また、C++では変数の寿命が尽きるときにデストラクタが呼ばれる。この性質を利用したデザインパターンがRAIIである。特に静的でないローカル変数(自動変数)の場合は、関数を抜けるときに必ず寿命が尽きるため、関数の脱出経路を問わずに確実に実行したい後始末処理(動的確保したメモリの解放やファイルのクローズなど)を記述するのによく使われる。

Java

Javaにおける変数は大別して4種類ある[2]

配列の要素は「インスタンス変数」に分類される。メソッドコンストラクタcatch節引数は「仮引数」に分類される。

Javaにおいては、メソッド内で宣言されている変数をローカル変数と呼ぶ。スコープの概念およびブロックの構文はC/C++とほぼ同様だが、C/C++と異なり、静的ローカル変数は定義できない。フィールド(インスタンス変数とクラス変数)に関しては、コンパイラが型に応じて適切な既定値を割り当てるが、ローカル変数に関しては既定値は割り当てられない[3]

下記の例において、3行目で宣言されているローカル変数iのスコープは3行目から7行目の中括弧までであり、5行目で宣言されているローカル変数jのスコープは5行目から6行目の中括弧までである。

class Foo {
    void bar() {
        int i = 1; // スコープの広いローカル変数
        {
            int j = 1; // スコープの狭いローカル変数
        }
    }
}

以下のような未初期化のローカル変数にアクセスするコードは、C/C++では未定義動作を引き起こすものの、コンパイルは通ってしまう。一方、Javaではコンパイルエラーとなる。

int x;
if (x == 0) {
}

Javaのメモリ解放はガベージコレクションによって行なわれる。Javaのローカル変数はスタック領域に確保されるが、クラスや配列などの参照型の場合、オブジェクト本体はヒープ領域に作成される。ローカル変数の寿命が尽きて、その変数が指していたオブジェクトがまったく参照されなくなったとしても、そのオブジェクトのメモリ領域が直ちに解放されるとは限らない。

{
    int[] a = new int[100];
}
// ブロックを抜けると変数 a は削除されるが、a が指していた配列オブジェクト本体の削除はガベージコレクタが担当する。

脚注

注釈

  1. ^ C言語の規格では「自動記憶域期間 (automatic storage duration) を持つオブジェクト」と表現されている。
  2. ^ C言語の規格では「静的記憶域期間 (static storage duration) を持つオブジェクト」と表現されている。
  3. ^ C++11よりも前の規格では、静的ローカル変数の初期化はスレッドセーフ性が保証されない。そのため、複数のスレッドから同時に初回アクセスが発生した場合、未定義動作を引き起こす。
  4. ^ C23規格でもautoキーワードによる型推論の機能が追加されたものの、C++11以降と異なり、記憶域クラス指定子としての用法も残されている。

出典

関連項目