スコープとエクステントまとめ

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 LispRubyだと、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:シングルスレッドだと線形リストでいけそうだが、マルチスレッドを実現している処理系ではツリーである必要がある。