プログラミング における変数 (へんすう、英 : variable )とは、高水準言語 のプログラム のソースコード において、扱うデータ を読み書きする記憶域 (storage) のことであり、固有の名前 (識別子 )によって識別される[ 1] 。変数を用いることで、データを一定期間記憶し必要なときに利用することができる。高水準言語において、変数は記憶装置 (メモリ)を抽象化する役割を果たす。
一人一人の人間が異なる名前によって区別されるように、変数も個々の名前によって区別される。これにより、プログラム上で複数のデータを容易に識別・管理することができる。変数の識別子 (identifier) のことを変数名 (variable name) という。一般に、変数が表すデータをその変数の値 (あたい、英 : value )という。
変数の宣言と代入と参照
通例、プログラムにおいて変数を扱うための主要な操作は、宣言・代入・参照の三つである。プログラミング言語 によって変数の扱い方は多少異なるので、ここでは一般的な事柄を述べるにとどめる。実際のプログラミング言語の仕様(規格票)の表現とは、いずれも一致しないかもしれない(一般に、言語によって用語の意味も異なる)。
宣言
プログラムの中でどのような名前の変数を用いるのかを、プログラミング言語の文法にのっとって明確に示すことを変数の宣言 (せんげん、英 : declaration )という。
ほとんどの静的型付け プログラミング言語では、変数を宣言する際にその名前だけでなくそのデータ型 も指定する必要がある。
これにより、各変数が扱うことのできるデータの種類を制限でき、プログラムの型安全性が保証できる。一般に、データに対して行なえる処理はデータ型によって異なるので、データ型を厳密に検査することで、誤ったプログラムを書いてしまうことを防止するのに役立つ。
動的プログラミング言語 では通例動的型付け が行なわれ、宣言なしに変数を使うことができる。
多くの静的型付けプログラミング言語では、プログラム側で型を明示しなくても、言語処理系(コンパイラ )が型システム に基づいて自動的に文脈から型を推論する機能があり、これを型推論 (type inference) と呼ぶ。型推論をサポートする言語では、変数宣言時の初期化式(右辺値)から型を推論し、型名の記述を省略することもできる。ただしバリアント型 (英語版 ) や動的型付けとは異なり、型推論により決定された型は不変であり、再代入によって変数が表す値の型が変わるようなことはない。関数型プログラミング言語 は標準的に型推論をサポートしているが、従来の手続き型プログラミング言語(オブジェクト指向言語を含む)にも型推論の機能が導入されていることが多い。
代入
宣言した変数に対して実際にデータを関連付けることを代入 (だいにゅう、英 : substitution / assignment )という。
プログラミング言語によっては、変数の宣言と代入を一度にまとめて行なうことができる。
変数を宣言せずにいきなり代入できる言語もあるが、これは宣言されていない変数に対して処理系が自動的に宣言を補ってくれていると考えることができる。
ある変数に対して初めて行う代入は、特に初期化 (しょきか、英 : initialization )という。
多くの手続き型言語 では、変数は複数回代入をすることができる。すでに代入を行なった変数に改めて代入をすると、その変数とそれまでのデータとの関連はなくなり、新しいデータと改めて関連付けされる。
関数型言語 では、一つの変数には一度しか代入できないものも多い。このような言語では、宣言と初期化を一緒に行なうのが一般的であり、また、一つの変数が常に同じ値を持つ(値が変化しない)ことが保証される(参照透過性 )。関数型言語では特にこの再代入を許可しない関連付けのことを、束縛 (binding) と呼ぶ。
参照
その変数に代入したデータを利用することを、変数の参照 (さんしょう、英 : reference )という。
一度も代入を行なっていない、つまり初期化していない変数を参照することは意味を成さず、不正である。例えば未初期化のローカル変数 を参照すると、C言語 やC++ では(コンパイルエラーにはならないものの)未定義動作 を引き起こし[ 2] 、Java やC# ではコンパイルエラーを引き起こす。
しかし一部のプログラミング言語における特定の変数では、明示的な初期化式がなくとも変数を定義した段階で、自動的に何らかのデータ(通例ゼロまたはゼロに準ずる値)が既定値として変数に関連付けられる。このような場合は、初期化を明示的に記述しないまま変数を参照できる。例えばC/C++の静的記憶域期間を持つ変数や、Java/C#のフィールド などが挙げられる。
スコープと生存期間
変数のスコープ (scope) あるいは可視範囲とは、変数がソースコード上の「どこから可視であるか」を表す概念である。一方、変数の生存期間 (lifetime) とは、変数が持っているその内容との対応付け (binding) が、再代入などが無ければ「いつまで保持され続けるか」を表す概念である。
スコープ
変数はその変数に対して定められたソースコードの特定の範囲内からしか「見えない」。すなわち、その変数が使用可能な範囲が、その変数のスコープである。例えばC言語 では、関数 の中で定義した変数(仮引数およびローカル変数 )はその関数の中でのみ使用できる。これにより、関数の外部から変数を使用されるのを防ぐことができる。また、スコープが異なれば、同じ名前の変数を定義することもできる。例えば関数f()
の内部で定義されたx
と、関数g()
の内部で定義されたx
は、それぞれ別々に領域が確保される。一般に、変数に限らず、なんらかの名前について「名前空間 を区切るもの」が「スコープ 」である。たとえば構造体 のメンバ名は、構造体ごとに名前空間を持つ[ 3] 。
一つのスコープにおいて同じ名前を複数の違うものに使うことは、許されていないことが多い。あるいは許されている場合は、後から現れたものによって、そこから後では前のものは隠蔽(シャドウ)される (en:Variable shadowing ) という規則の場合もある。
さらに、スコープが入れ子になっている場合にも似たようなケースがある。たとえば、C言語の「グローバル」「ソースファイルごと」「ブロック内(ローカル変数 )」というスコープは、それぞれ入れ子になっていて、かつ、内側からは外側のスコープにある名前が見える(内側からは外側のスコープにある名前は見えない、というような名前空間もある)。そのような時、外側に既にある名前と同じ名前は内側では使えないという規則のこともあれば、内側で同じ名前を使うと、外側のものは隠蔽されるという規則の場合もある。
プログラミング言語には多くの種類のスコープがあるが、詳細については「スコープ (プログラミング) 」の記事を参照のこと。
以上のように、スコープは実行時のものではなく、基本的にソースコード上で静的に定まるものである(「動的スコープ 」という例外もあるが[ 4] )。
生存期間
変数の生存期間 (英 : lifetime )はプログラム実行時に記憶域割り当てが保証されている範囲である[ 5] 。
変数が生存期間内にあるとき、変数は存在し、不変のアドレスをもち、最後に書き込まれた値を取り出せる[ 6] 。すなわち「変数」としての役割を果たす。一方生存期間外にあるとき、そのアクセスは保証されない(未定義動作 [ 7] あるいは禁止)。
生存期間という概念は記憶域すなわちメモリの確保・解放を可能にする。自動変数では生存期間の終了時にメモリが即時自動解放され、ガベージコレクション をサポートする言語あるいは環境では生存期間外にある変数のメモリがガベージコレクタによって事後的に自動解放される。逆にプログラム全体で値を保持し続ける必要がある静的変数では、実行全体にわたる生存期間の設定によりこれを可能にしている(例: グローバル変数 )。
言語や規格によって呼称は異なる。一般的な別名として寿命 、C言語 およびC++ では生存期間 (lifetime) や記憶域期間 (storage duration) [ 8] 、Common Lisp ではエクステント (extent) [ 9] 、C# では有効期間 (lifetime) [ 10] と呼ばれる。
変数のスコープ はアクセス可能範囲に関する用語であり、メモリ割り当て保証範囲に関する生存期間とは別の概念である。例えばC/C++は静的ローカル変数をサポートし、関数内のローカル変数に記憶域クラス指定子static
[ 11] [ 12] を付けることで、その変数は静的記憶域期間を持つようになり、「スコープを脱出すると参照はできなくなるが、プログラムが終了するまでメモリ上に残り続ける変数」を定義できる。
変数の生存期間とは、プログラムの実行時に、その名前とそれが指すオブジェクトという対応付けが、いつ始まり、(再代入などが無い限り)いつまで保持されるか、ということである。
C言語における関数の仮引数や、自動記憶域期間を持つローカル変数 (static
修飾されていないローカル変数[ 注釈 1] )の生存期間は、その関数を呼び出してから抜けるまでである。これは、C言語では関数呼び出しから一旦抜けてしまうと、そこに戻ってくることは無い(setjmp.h (英語版 ) 内のsetjmp/longjmpを使えば不可能ではないが、「深い方」へのジャンプは禁止されている)からである。
生存期間は変数とその中身(オブジェクト[ 注釈 2] )の対応付け (binding) についての概念である。クロージャ などの変数キャプチャにより、その変数へのアクセスがその後もあるかもしれない場合は、その関数呼び出しを抜けてもその対応付け(束縛、bindingなどとも)は保持されなければならない。クロージャないしそのようなものがある言語では、そのためにローカル変数でもエクステントは(アクセスされる可能性がある限り)延長される。そのようなエクステントをinfiniteあるいはindefiniteのエクステント(訳して、無限の、あるいは、無制限の存在期間、など)という。
なお、C言語の関数におけるローカル変数をstatic
修飾すると、グローバル変数と同様の生存期間(静的記憶域期間)になる。一方、C言語のグローバル変数をstatic
修飾すると、変数のスコープをファイルスコープに制限する(内部リンケージにする[ 注釈 3] )。これは単にstatic
という同一のキーワードを、文脈から区別が可能だから流用しているというだけで、それぞれの意味は全く違う(後者はfilescope
といったような別の表現にするのが本来は適切だっただろう [独自研究? ] )。この流用が、おそらくスコープとエクステントを多くのプログラマが混同する原因の一つである [独自研究? ] [ 14] 。
脚注
注釈
^ C17規格までのC言語およびC++03規格までのC++において、auto
修飾されたローカル変数は自動記憶域期間を持つ自動変数になるが、ローカル変数は既定で自動変数となるため、auto
キーワード は通例使われない。なおC++において、C++11 規格以降はauto
キーワードの意味が変更されており、型推論のプレースホルダーに使われる。C言語においても、C23規格以降はauto
キーワードの意味が変更され、型推論のプレースホルダーに使われるようになる予定である[ 13] 。
^ いわゆるオブジェクト指向 におけるオブジェクトではなく、プログラミング言語で値を表すものや、メモリ上にあるデータ(メモリオブジェクト)などといったものの総称である。
^ C言語のリンケージ指定の用法におけるstatic
キーワードの対義語はextern
キーワードである。グローバル変数や関数はデフォルトで外部リンケージであり、他のソースファイルからもそれらの「宣言」を記述するだけでアクセスできるようになるが、宣言をextern
修飾することで外部リンケージであることを明確に示すこともできる。不完全型の外部リンケージ変数宣言の場合はextern
修飾は必須ではないが、完全型の外部リンケージ変数宣言の場合は定義と区別するためにextern
修飾が必須となる。extern
キーワードは通例ヘッダーファイルで前方宣言 する際に使用される[ 11] 。
出典
関連項目