スコープとエクステントまとめ
ABCLのソースを読んでるうちにスコープとエクステントについてだんだんわかってきたので、自分なりに少しまとめてみた。合ってるかどうかはわからないので、すべてに?がついていると考えてください。
- スコープ
- アクセス可能な場所的範囲
- エクステント
- アクセス可能な時間的範囲
言語 | 種別 | スコープ | エクステント | コンパイラ・インタプリタでの検索方法(典型例) | 実装上の領域(典型例) |
---|---|---|---|---|---|
C | ローカル(auto)変数 | ブロック内 | ブロックに入って時点から出た時点まで | 関数の変数宣言リスト | スタック |
C | ブロック内のstatic変数 | ブロック内 | プログラムの開始から終了まで | 関数の変数宣言リスト | データ領域 |
C | トップレベルのstatic変数 | ファイル内 | プログラムの開始から終了まで | コンパイル単位の変数宣言リスト | データ領域 |
C | グローバル変数 | ファイル内、extern宣言により別ファイルの変数を参照可 | プログラムの開始から終了まで | コンパイル単位の変数宣言リスト | データ領域(シンボルexportあり) |
Java | ローカル変数 | ブロック内(内側のブロックで上書き可) | ブロックに入って時点から出た時点まで | ブロックの変数宣言リスト | スタック |
Java | インスタンス変数 | アクセス修飾子で指定した範囲 | インスタンスの生成から消滅(GCに回収される)まで | インスタンスフィールドのリスト | ヒープ領域 |
Java | クラス変数 | アクセス修飾子で指定した範囲 | クラスロード時からプログラム終了時 | クラスフィールドのリスト | ヒープ領域 |
Java | TLS | 保持するThreadLocalオブジェクトを参照できる範囲 | 最初に定義されてからスレッド消滅まで | ユーザがプログラムで記述する | スレッドローカル領域 |
Common Lisp | 変数 | 定義されたS式内(内側のS式で上書き可) | ブロックに入ってからGCに回収されるまで | レキシカル環境ツリー | ヒープ領域 |
Common Lisp | 特殊変数 | 定義されたS式内(内側のS式で上書き可) | 最初に定義されてからプログラム終了まで | 特殊変数バインドのツリー*1 | ヒープ領域 |
Ruby | ローカル変数 | 宣言位置からブロックの終わりまで | 最初に定義されてからGCに回収されるまで | レキシカル環境ツリー?? | ヒープ領域 |
Ruby | インスタンス変数 | クラス定義内 | インスタンスの生成から消滅(GCに回収される)まで | インスタンス変数リスト | ヒープ領域 |
Ruby | クラス変数 | クラス定義内 | クラスの定義からプログラム終了まで | クラス変数リスト | ヒープ領域 |
Ruby | グローバル変数 | プログラム全体 | 最初に定義されてからプログラム終了まで | グローバル変数専用リスト | ヒープ領域 |
クロージャを実装するときに必要とされる「無限エクステント」の変数は、必要なくなるまでメモリ上に居ないといけない(ブロックから抜けたときに勝手に消えたりしてはいけない)ので、スタック領域ではだめで、ヒープ領域においておく必要がある。
Javaのローカル変数は、スタック上にあることになっているので、インナークラスでクロージャを素直に書くことはできない。下記がその例。
// 現行のJavaでクロージャができそうでできない例 public class Test { Runnable test() { int x = 0; // ←これはダメ。下記の理由でこれはfinalである必要がある。 return new Runnable() { void run() { System.out.println("" + x); x++; // ←これはダメ。外側で宣言されたxの領域は、メソッドtest()が返った時点でなくなる。 // Runnableオブジェクトがnewされる時点で、xの値が定数になっている必要がある。 // 従って、外側ではfinal宣言されていなければならない。 } } } }
上記のソースコードを実装したければ、変数xをスタック領域以外の場所に置かなければならない。
Common LispやRubyだと、xがヒープ(?)にあるため、testが返ってもxの領域は残っている。
(defun test () (let ((x 0)) (lambda () (format t "~A~%" x) (incf x))))
クロージャではない例だが、このstatic変数xの生存期間はプログラムの終了までなので、「無限エクステント」というのかもしれない。しかし、上記Common Lispの例の中のxがtestが呼び出されるたびに新たな領域に作成されるのに対し、下記Cの例のxは常に同じメモリを指しているので、本質的に別物だ。
int *test(void) { static int x = 0; return &x; }
*1:シングルスレッドだと線形リストでいけそうだが、マルチスレッドを実現している処理系ではツリーである必要がある。