****** 変数 ****** ============== 変数について ============== 数学の変数は、いろんな値を取る可能性があることを表している。場合分けなどで値の取りうる範囲が限定されると、それ以後はその範囲以外の値を取ることはない。例えば「x=0の場合」と書いてあれば、それ以後xが0以外の値を取ることはない。 関数型や論理型言語における変数は、数学の変数とほぼ同じものである。変数名は値に対して束縛され、いったん束縛されれば値が変化することはない。ただし、環境が切り替われば同じ変数名に対して別の束縛が使われるので、見かけ上値が変化することはある。 命令型(手続き型)言語では、変数とは値を記憶する場所のことである。変数名は記憶場所に対して束縛される。その場所に記憶されている値は代入によって変化する。 .. figure:: variable-binding.png :scale: 60% :align: center パラダイムによる変数の束縛の違い 以下では、主に命令型(手続き型)言語の変数について説明する。 ==== 型 ==== Fortran, Pascal, C, Java など多くの言語では、各変数には型を宣言し、その型の値以外は記憶できない。それに対して Lisp やスクリプト言語などは変数に型がなく、任意の値を記憶することができる。 型については次回に詳しく説明する。 .. _left-value: ======== 左辺値 ======== 変数の実体は、プログラムを実行中にデータを格納するための記憶領域である。 式の中に変数名が出現すると、次のようにして評価される。 1. 変数名に束縛された変数の記憶領域の番地を得る。 2. その記憶領域から値を取り出す。 ただし、代入文の左辺に変数名が出現した時は、値ではなく、値を書き込む番地が必要になる。このため、記憶領域の番地のことを変数の左辺値(:index:`left value`)と呼ぶこともある。 ================ 記憶領域の管理 ================ コンパイラやインタプリタは変数に記憶領域を割り当てるが、その割り当て方には次のような種類がある。なお、ここでは変数だけについて説明しているが、実際には変数以外のいろいろなデータも混ざって記憶されている。 静的データ領域(:index:`static data area `) ------------------------------------------------------------------- プログラムの実行中ずっと存在している変数のための領域。どの変数がどの番地に対応するかは固定されていて、静的に(コンパイル時に)決まる。 ただし、コンパイル時に必要な記憶容量を計算するためには、変数に型が宣言されている必要がある。 スタック(:index:`stack`) ------------------------ 関数の仮引数やローカル変数など、関数呼び出しに伴って領域の確保と解放を行う変数のための領域。 * 再帰呼び出しが自然にできる。 * 動的スコープで有効な束縛を探すには、上から順に見ていけばよい。静的スコープでは、ブロックの包含関係があるところだけを探すのに工夫が必要。 ヒープ(:index:`heap`) --------------------- 動的に生まれたり消えたりする変数のための領域。必要になった時に記憶領域を確保し、不要になったら解放する。 言語によっては、不要になったら解放する処理をプログラマが明示的に書かなくてはいけない。すると、解放するのを忘れて、実際に使用されている領域は少なくても新しく確保する領域がなくなってしまうことがある。これをメモリリークと呼ぶ。 メモリリークを防ぐための機能として、不要になった領域を自動的に解放するゴミ集め(:index:`garbage collection`)がある。あるデータが不要であることは、そのデータにアクセスする手段がないことで判断できる。 代表的なゴミ集めの方式には次のようなものがある。 Reference count 記憶領域にカウンタを付加し、新たにその領域への参照が生じたらカウンタを1増やし、参照がなくなったらカウンタを1減らす。カウンタの値が0になったらその領域を解放する。 Mark and sweep 変数名が束縛されている記憶領域など、必要なのが明らかなところから出発し、そこから参照をたどって到達できる領域にマークを付けていく。次に、記憶領域全体を端から順に見ていって、マークの付いていない領域を解放する。 Copying 記憶領域を2分割し、通常は片方だけを使用する。利用可能な領域がなくなったら、mark and sweep と同じように参照をたどり、必要な部分をもう片方の記憶領域にコピーする。これによりゴミ集めとコンパクションが同時にできる。 上記のようなゴミ集め方式を素直に実装すると、ゴミ集めを開始する時に通常のプログラムの実行を中断し、ゴミ集めが終わってから再開する必要がある。しかしリアルタイムシステムではプログラムの実行が停止すると困るので、通常のプログラムの実行と並行に実行できるように工夫した方式も存在する。 .. admonition:: 課題7 :class: exercise Reference count 方式のゴミ集めでは、不要な領域なのにカウンタの値が0にならない場合が起こりうる。それはどんな場合か? :ref:`【解答例】 ` .. admonition:: 課題8 :class: exercise Mark and sweep 方式のゴミ集めを通常のプログラムの実行と並行して行うと、どんな不都合が起こるか? :ref:`【解答例】 ` .. _extent: ========== 生存期間 ========== 変数が値を保持している期間を生存期間(:index:`extent`)と言う。生存期間とスコープは必ずしも一致しない。 .. admonition:: 静的なローカル変数 CやPHPで ``static`` 宣言された局所変数は、スコープは関数の中だが、生存期間はプログラム全体が終了するまで。 .. code-block:: ruby function test() { static $a = 0; echo $a; $a++; } .. admonition:: 課題9 :class: exercise 静的なローカル変数はどのような場面で有効か、具体的な応用例を一つ挙げよ。 :ref:`【解答例】 ` .. warning:: ローカル変数でも、関数がクロージャとして実行される場合(第8回に説明)は生存期間が延びる。また、記憶領域をスタックではなくヒープに置く必要がある。