グローバル変数
グローバル変数(大域変数、英: global variable)は、コンピュータプログラミングにおいて全てのスコープからアクセスできる変数のことである。グローバル変数の寿命は、プログラムの生存期間と同じである。対する語は、ローカル変数である。スコープも参照。
概要
[編集]一般にグローバル変数は、その非局在的な性質ゆえに、悪い作法だと考えられている。すなわち、グローバル変数は潜在的にどこかで変更される可能性があり、またプログラムの一部はそれに依存してしまう恐れがあるからである。グローバル変数はそれゆえ相互依存を生み出す無限の可能性を持っており、相互依存が高まることは複雑性を増大することにつながる。遠隔作用 (action at a distance) を参照。
しかしグローバル変数が適する状況もある。例えば、システム起動時に一度だけファイルから読み出し、以降は様々な関数を通して継続的によく使われるような設定情報を格納する変数を、関数に毎回引数として渡すことを避けるために使用できる。また、並列実行するスレッドやシグナルハンドラのように、呼び出し側/被呼び出し側の関係を持たないコード同士でセクション間情報を受け渡すためにも広く使われる。利用可能なメモリ容量が少ない組み込みシステムのような厳しい環境では、グローバル変数を使用せざるをえない場面もある。
また、C言語のように名前空間を持たないプログラミング言語では、ソースファイルを超えてグローバル変数を共有する際、名前衝突の問題も発生しうる。
各々のソースファイルが暗黙の名前空間(無名名前空間)を定義するような言語[要説明]では、グローバルな名前空間を持つ言語[要説明]に見られる問題の多くが解消されているが、一部の問題は適切なカプセル化を行わなければ解決できない。(ミューテックスのような)適切なロック無しにグローバル変数を使用しているコードは、保護メモリ内の読み出し専用値以外はスレッドセーフではない。
ハンガリアン記法では、グローバル変数を使用していることをソースコード上で明確化するために、"g_"の接頭辞を付ける。
例
[編集]C++におけるグローバル変数の例を示す。
#include <iostream>
int global = 3; // これはグローバル変数。
void ChangeGlobal() {
global = 5; // 関数でグローバル変数を変更。
}
int main() {
std::cout << global << std::endl; // もう一つの関数でグローバル変数を参照。
ChangeGlobal();
std::cout << global << std::endl;
return 0;
}
変数global
はグローバルスコープを持ち、全ての関数から直接アクセスすることができる。
この出力結果は、以下のようになる。
3 5
グローバル変数を使用すると、ソフトウェアのソースコードを理解することが困難になる。グローバル変数の値はプログラムのどこからでも参照・変更できるため、グローバル変数がどのように使用されているか、またグローバル変数の値(すなわち状態)がどのように変化するかを理解するためには、プログラム全体を把握しなければならないからである。
以下にグローバル変数により難解になる例を示す。
int x, y, z; // グローバル変数宣言
void FunctionA() { x = 2; }
void FunctionB() { y = x * 10; }
void FunctionC() { z = (x + y) / 2; }
int main() {
FunctionA(); // グローバル変数xを書き換える
FunctionB(); // グローバル変数xを元にグローバル変数yを書き換える
FunctionC(); // グローバル変数x, yを元にグローバル変数zを書き換える
return 0;
}
上記をローカル変数を用いて書きなおした例を示す。
int FunctionA() { return 2; }
int FunctionB(int x) { return x * 10; }
int FunctionC(int x, int y) { return (x + y) / 2; }
int main() {
int x = FunctionA(); // ローカル変数xを書き換える
int y = FunctionB(x); // ローカル変数xを元にローカル変数yを書き換える
int z = FunctionC(x, y); // ローカル変数x, yを元にローカル変数zを書き換える
return 0;
}
ローカル変数を用いた例ではどの変数が、どの関数呼び出しに作用し、どの関数呼び出しの結果を保持しているかは一目瞭然である。また、いずれの関数も状態を持たず独立しているため、入出力が明確であり、モジュールとしての再利用性も高い。対して、グローバル変数を使用した例では、関数の宣言部(インターフェイス)および呼び出し側だけを見てどの関数がどの変数に影響を与えるかを判断することはできず、入出力が不明瞭であり、モジュールとしての再利用も困難である。また、ローカル変数を用いた例では関数の呼び出し順序を入れ替えることは引数と戻り値だけ意識すれば良く容易であるのに対して、グローバル変数を用いた例ではグローバル変数の作用を全て把握する必要があるため困難である。特にこの困難度合いは、グローバル変数の数とグローバル変数を操作する可能性のある関数の数に比例する。
(DLLのような)多くのシステムは他のモジュールにあるグローバル変数の参照を直接サポートしないため、グローバル変数が多用されているコードを分割して再利用ライブラリにすることは非常に困難である。また、グローバル変数が他のローカルまたはオブジェクトスコープ変数として代用するには危険な名前を作るために起こる名前問題を導いてしまうことがある。グローバル変数と同じ名前のローカル変数は、一般にそのローカル変数のスコープにおいてグローバル変数を隠匿する。そのため、さらにコードの理解を困難にする。グローバル変数への値の書き込みは理解と予測を難しくする副作用を生み出す。グローバル変数の利用はユニットテストを目的とするコード分割をより難しくする。それゆえ、それらは直接にコードの質の低下を助長する。
C言語およびC++の規格では、グローバル変数はプログラム開始処理以前に初期化されることが規定されているが、各々の初期化順序は規定されていない。したがって、グローバル変数の初期化順序に依存するようなコードを書いてしまうと、期待と異なる動作を引き起こすことにつながる。同様にC++の静的メンバー変数も、初期化順序は規定されていない。
JavaやC#のような後発のオブジェクト指向言語はグローバル変数を持たないが、クラスの静的フィールド(クラス変数)がグローバル変数に相当する。静的フィールドは一度だけ呼び出される静的イニシャライザまたは静的コンストラクタにより初期化処理を制御することができるが、グローバル変数と同じく非局在的な性質を内包しているため、多用するとコード品質の低下を招く。特に、再代入可能な静的フィールドを外部に公開することは避けるべきとされている[1]。