Rubyには以下のようにかなりたくさんの種類の変数・定数が存在する。 スコープの大きいほうから並べよう。
インスタンス変数については既に第2章『オブジェクト』で説明した。 本章では残りのうち
について解説する。 また残るローカル変数については第三部で話す。
本章の解析対象はvariable.c
である。
まずそこからエントリポイントとなるAPIを紹介しておこう。
VALUE rb_iv_get(VALUE obj, char *name) VALUE rb_ivar_get(VALUE obj, ID name) VALUE rb_iv_set(VALUE obj, char *name, VALUE val) VALUE rb_ivar_set(VALUE obj, ID name, VALUE val)
これは既に解説したインスタンス変数のアクセスAPI。
variable.c
に定義があるのでいちおう載せておいた。
VALUE rb_cv_get(VALUE klass, char *name) VALUE rb_cvar_get(VALUE klass, ID name) VALUE rb_cv_set(VALUE klass, char *name, VALUE val) VALUE rb_cvar_set(VALUE klass, ID name, VALUE val)
クラス変数アクセスAPI。クラス変数はクラスに属するものなのでクラスを引
数に取る。どうやら変数アクセスAPIにはrb_Xv
とrb_Xvar
というパターンがあ
るらしい。その違いは変数名の型で、ユーザが使いやすいほう(char*
)が関
数名が短く、比較的内部用のもの(ID
)は関数名が長い。
VALUE rb_const_get(VALUE klass, ID name) VALUE rb_const_get_at(VALUE klass, ID name) VALUE rb_const_set(VALUE klass, ID name, VALUE val)
定数アクセスAPI。
定数もクラスに属するのでクラスを引数に取る。
rb_const_get()
はスーパークラスも辿り、
rb_const_get_at()
は辿らない。つまり引数のklass
だけを調べる。
struct global_entry *rb_global_entry(ID name) VALUE rb_gv_get(char *name) VALUE rb_gvar_get(struct global_entry *ent) VALUE rb_gv_set(char *name, VALUE val) VALUE rb_gvar_set(struct global_entry *ent, VALUE val)
そして最後にグローバル変数アクセスAPI。struct global_entry
を
介しているところが他の変数とちょっと違う。そうなっている理由は
実装のところで話す。
変数の話をするうえで最大のポイントとなるのは、「変数はどこに、どうやっ て記憶されているか」である。つまりデータ構造だ。
二番目に参照の振舞いである。Rubyの変数や定数はどれも継承してみたり 「外」を見てみたりとクセが強いので、 「こういうときはこうなるんだから実装はこうじゃなきゃおかしい!」 というふうに仕様と比べ合わせて考えていくと迷わずに済む。
クラス変数はクラスに属する変数である。クラスと、そのインスタンスの 両方からアクセスできる。JavaやC++で言うスタティック変数だ。だが 「インスタンスから」とか「クラスから」とかいうのは評価器にある情報で あって今はそういうものは存在していない。だからCレベルからはアクセス 範囲なんてないのと同じである。ここでは変数の格納形式だけに焦点を 絞って見ていこう。
クラス変数を参照するAPIはrb_cvar_get()
である。そしてまた短縮版の
rb_cv_get()
もあり、関数名が長いほうがID
、短いほうがchar*
を
引数に取る。ID
をとるほうがより内部に近いと考えられるから、
rb_cvar_get()
を見てみよう。
1508 VALUE 1509 rb_cvar_get(klass, id) 1510 VALUE klass; 1511 ID id; 1512 { 1513 VALUE value; 1514 VALUE tmp; 1515 1516 tmp = klass; 1517 while (tmp) { 1518 if (RCLASS(tmp)->iv_tbl) { 1519 if (st_lookup(RCLASS(tmp)->iv_tbl,id,&value)) { 1520 if (RTEST(ruby_verbose)) { 1521 cvar_override_check(id, tmp); 1522 } 1523 return value; 1524 } 1525 } 1526 tmp = RCLASS(tmp)->super; 1527 } 1528 1529 rb_name_error(id,"uninitialized class variable %s in %s", 1530 rb_id2name(id), rb_class2name(klass)); 1531 return Qnil; /* not reached */ 1532 } (variable.c)
この関数はklass
のクラス変数id
を参照する。
rb_raise()
、つまりエラー処理をする関数は基本的に無視してよいという
ことは既に書いた。今回出てきたrb_name_error()
も例外を発生する関数
なので同じく無視できる。基本的にruby
では_error
で終わる関数は全て
例外を発生すると考えてよい。
そういうものを全部削ると、klass
のスーパークラスを順番に辿りつつ
iv_tbl
を検索するだけだな、とわかる。……と言うところで「えっ?」と
思ってほしい。iv_tbl
というのはインスタンス変数のテーブルのはずだ。
ということは、実はクラス変数はインスタンス変数のテーブルに記憶されて
いるのである。
こういうことができるのはID
を作るときの同値判定に変数のプリフィクスまで
含まれているからだ。つまり"@var"
と"@@var"
はrb_intern()
すると別のID
に
なるからだ。またRubyレベルでは変数の種類がプリフィクスだけで決まるので
@var
という名前のクラス変数を参照することはどうやってもできない。だから
こそこんなことをしてもうまくいく。
唐突だがここでstruct RClass
のメンバを思い出してほしい。
struct RClass
にあるのは、basic
を除くと
VALUE super
struct st_table *iv_tbl
struct st_table *m_tbl
の三つだ。そこで考えるに、
struct RClass
にあるようには見えないiv_tbl
に同居していたじゃあもしかして定数も……
rb_const_set()
は定数に値を代入する関数である。
クラスklass
の定数id
に、val
をセットする。
1377 void 1378 rb_const_set(klass, id, val) 1379 VALUE klass; 1380 ID id; 1381 VALUE val; 1382 { 1383 mod_av_set(klass, id, val, Qtrue); 1384 } (variable.c)
ほとんどmod_av_set()
に委譲されている。
1352 static void 1353 mod_av_set(klass, id, val, isconst) 1354 VALUE klass; 1355 ID id; 1356 VALUE val; 1357 int isconst; 1358 { 1359 char *dest = isconst ? "constant" : "class variable"; 1360 1361 if (!OBJ_TAINTED(klass) && rb_safe_level() >= 4) 1362 rb_raise(rb_eSecurityError, "Insecure: can't set %s", dest); 1363 if (OBJ_FROZEN(klass)) rb_error_frozen("class/module"); 1364 if (!RCLASS(klass)->iv_tbl) { 1365 RCLASS(klass)->iv_tbl = st_init_numtable(); 1366 } 1367 else if (isconst) { 1368 if (st_lookup(RCLASS(klass)->iv_tbl, id, 0) || 1369 (klass == rb_cObject && st_lookup(rb_class_tbl, id, 0))) { 1370 rb_warn("already initialized %s %s", dest, rb_id2name(id)); 1371 } 1372 } 1373 1374 st_insert(RCLASS(klass)->iv_tbl, id, val); 1375 } (variable.c)
今回も例外チェックと警告は無視する。具体的にはrb_raise()
と
rb_error_frozen()
、rb_warn()
を無視する。すると残りはこれだけだ。
if (!RCLASS(klass)->iv_tbl) { RCLASS(klass)->iv_tbl = st_init_numtable(); } st_insert(RCLASS(klass)->iv_tbl, id, val);
ここから明らかなように、定数もやはりインスタンス
変数テーブルに同居しているのだ。つまりstruct RClass
の
iv_tbl
には
の三種類がごっちゃになって記憶されているということになる。
以上で定数の格納形式についてはわかった。 今度は頭を切り替えて定数の仕様のほうに目を向けていくことにしよう。
rb_const_get()
定数を参照する関数rb_const_get()
を読んでみる。
この関数はクラスklass
の定数id
を参照する。
1156 VALUE 1157 rb_const_get(klass, id) 1158 VALUE klass; 1159 ID id; 1160 { 1161 VALUE value, tmp; 1162 int mod_retry = 0; 1163 1164 tmp = klass; 1165 retry: 1166 while (tmp) { 1167 if (RCLASS(tmp)->iv_tbl && st_lookup(RCLASS(tmp)->iv_tbl,id,&value)) { 1168 return value; 1169 } 1170 if (tmp == rb_cObject && top_const_get(id, &value)) return value; 1171 tmp = RCLASS(tmp)->super; 1172 } 1173 if (!mod_retry && BUILTIN_TYPE(klass) == T_MODULE) { 1174 mod_retry = 1; 1175 tmp = rb_cObject; 1176 goto retry; 1177 } 1178 1179 /* Uninitialized constant */ 1180 if (klass && klass != rb_cObject) { 1181 rb_name_error(id, "uninitialized constant %s at %s", 1182 rb_id2name(id), 1183 RSTRING(rb_class_path(klass))->ptr); 1184 } 1185 else { /* global_uninitialized */ 1186 rb_name_error(id, "uninitialized constant %s",rb_id2name(id)); 1187 } 1188 return Qnil; /* not reached */ 1189 } (variable.c)
いろいろあって邪魔だ。まず少なくとも後半のrb_name_error()
はまとめて
消せる。真ん中のmod_retry
のあたりはどうもモジュールを特別扱いしている
ようだ。これもとりあえず消してしまおう。するとここまで減らせる。
VALUE rb_const_get(klass, id) VALUE klass; ID id; { VALUE value, tmp; tmp = klass; while (tmp) { if (RCLASS(tmp)->iv_tbl && st_lookup(RCLASS(tmp)->iv_tbl,id,&value)) { return value; } if (tmp == rb_cObject && top_const_get(id, &value)) return value; tmp = RCLASS(tmp)->super; } }
これなら一目でわかる。引数のklass
のスーパークラスをたどりながら
iv_tbl
を検索しているのだ。つまり
class A Const = "ok" end class B < A p(Const) # 参照できる end
という場合だ。
唯一気になるのはtop_const_get()
のところである。rb_cObject
で限定してい
るのだからtop
は「トップレベル」の意味だろう。念のため確認しておくと、
トップレベルでのクラスはObject
だ。それは「C
を定義するクラス文の中では
クラスがC
になる」というのと同じ意味で、「トップレベルのクラスは
Object
」なのである。
# トップレベルのクラスはObject class A # クラスはA class B # クラスはB end end
つまりtop_const_get()
はトップレベル限定で特殊な操作をするようだと
予想できる。
top_const_get()
そのtop_const_get()
を見てみよう。
定数id
を検索しklassp
に書き込んで返す。
1102 static int 1103 top_const_get(id, klassp) 1104 ID id; 1105 VALUE *klassp; 1106 { 1107 /* pre-defined class */ 1108 if (st_lookup(rb_class_tbl, id, klassp)) return Qtrue; 1109 1110 /* autoload */ 1111 if (autoload_tbl && st_lookup(autoload_tbl, id, 0)) { 1112 rb_autoload_load(id); 1113 *klassp = rb_const_get(rb_cObject, id); 1114 return Qtrue; 1115 } 1116 return Qfalse; 1117 } (variable.c)
rb_class_tbl
は第4章『クラスとモジュール』で出てきた。トップレベルのクラスを記
憶しているテーブルである。例えば組み込みクラスのString
やArray
など
がここに登録されている。だからトップレベルの定数を検索するときはこのテー
ブルも探さなければ不十分だ。
それから次にあるのがオートロード関係の関数である。トップレベルの定数は 初めて参照されたときに自動的にロードするライブラリを登録できるようになっ ている。例えばこんなふうに使う。
autoload(:VeryBigClass, "verybigclass") # 中でVeryBigClassを定義する
こう書いておくとVeryBigClass
を初めて参照したときにライブラリ
verybigclass
がrequire
され、その中でVeryBigClass
が定義されていれば何事
もなく続きを実行できる。ライブラリが大きすぎてロードに時
間がかかるときに使うと効果的である。
このオートロードを処理しているのがrb_autoload_xxxx()
だ。本書ではオート
ロードについてはこれ以上話さない。どうも近いうちに仕様の大変更がありそ
うだからだ。
ところで、外のクラスの定数を検索するコードはどこに行ってしまったのだろ う。定数はまず外方向を検索し、次にスーパークラス方向を検索するはずだ。
実は、それがまだ説明できない部分なのである。外のクラスはプログラムの場
所によって変わる。つまりプログラムのコンテキストに依存しているのだ。だ
から評価器の内部状態を合わせて考えないと扱えないのである。具体的に言う
と、外のクラスまで扱っているのはeval.c
のev_const_get()
だ。定数の話には
第三部でケリをつけることにしよう。
グローバル変数はどこからでもアクセスできる。それは逆に言うと 一切アクセスを限定する必要がないということである。どこに所属 するわけでもないからテーブルが一ヶ所あればそれで済むし、なんの チェックもしなくていい。だから実装は非常に簡単だ。
それでもややコードが多いのは、Rubyのグローバル変数にはただの 変数とは言い難いギミックが搭載されているからだ。 こんな機能があるのはグローバル変数だけである。
alias
文で別名を定義できる簡単に説明しよう。
alias $newname $oldname
これで以後$newname
を$oldname
の代わりに使えるようになる。
変数のalias
は主に「記号変数」対策のために用意されている。
記号変数とはPerlから継承した$=
とか$0
といった変数のことだ。
$=
は文字列比較のとき大文字小文字の区別をするかどうか決める。
$0
はメインのRubyプログラム名を表す。記号変数はこの他にもいくつか
あるのだが、なにぶん名前部分が一文字なので、Perlなどを知らない人に
とっては覚えるのも大変である。そこでもう少しわかりやすい
別名をつけられるようにしてあるというわけだ。
とは言っても現在はそもそも記号変数自体推奨されなくなってきており、
次々と適当なモジュールの特異メソッドなどに移されている。
$=
などは2.0では機能自体が廃止されそうな雰囲気だ。
グローバル変数では変数への代入・参照をフックできる。
いちおうRubyレベルからもフックはかけられるのだが、どちらかというと
Cレベルで$KCODE
のようなシステム用の特殊変数を用意するために使いたかった
のではないかと思う。$KCODE
というのはそのときインタプリタが扱う文字列
エンコーディングを入れておく変数で、厳密には"EUC"
とか"UTF8"
という
ような特定の文字列しか代入できない。ただそれでは面倒なので"e"
とか
"u"
でも代用できるようになっている。
p($KCODE) # "NONE"(デフォルト) $KCODE = "e" p($KCODE) # "EUC" $KCODE = "u" p($KCODE) # "UTF8"
代入にフックをかけられればこういうことも簡単にできるのは
わかると思う。ちなみに$KCODE
のKは漢字のKらしい。
まあalias
にしてもフックにしても、そもそもグローバル変数自体ロクに使わ
ないのでどうでもいい機能だ。使わない機能は適当に終わらせて、パーサや評
価器の解析にページを使いたいものである。そういうわけで以下の説明も投げ
遣り度85%で進行したいと思う。
変数の仕組みを見ていくときのポイントは格納形式だと言った。 まずはグローバル変数に使われる構造をしっかり把握してほしい。
21 static st_table *rb_global_tbl; 334 struct global_entry { 335 struct global_variable *var; 336 ID id; 337 }; 324 struct global_variable { 325 int counter; /* リファレンスカウンタ */ 326 void *data; /* 変数の値 */ 327 VALUE (*getter)(); /* 変数の参照に使う関数 */ 328 void (*setter)(); /* 変数への代入で使う関数 */ 329 void (*marker)(); /* 変数をマークする関数 */ 330 int block_trace; 331 struct trace_var *trace; 332 }; (variable.c)
rb_global_tbl
がメインテーブルである。全てのグローバル変数は
差別なくこのテーブルに記憶される。テーブルのキーはもちろん
変数名(ID
)。そして値はstruct global_entry
とstruct global_variable
が
セットでグローバル変数一つを表現している(図1)。
図1: グローバル変数テーブルの実行時イメージ
変数を表現する構造体が二つに分かれているのはalias
の実装のためである。
alias
が設定されたら、二つのglobal_entry
から同じ
struct global_variable
を指すようにする。
またstruct global_variable
のメンバcounter
はその時に必要になるリファ
レンスカウンタである。リファレンスカウントの概念は既に前章『ガ−ベージコレクション』で
説明した。簡単に復習すると、これから参照しようとするときにはカウンタを
+1 する。もういらなくなったらカウンタを -1 する。カウントがゼロになっ
たらもう誰も必要としていないのでfree()
していい。
それから最後に、Rubyレベルでフックが定義されているときはさらに
struct global_variable
のメンバtrace
にsturct trace_var
のリストが
格納されるのだが、これについては話さない。struct trace_var
ごと省略する。
グローバル変数は参照だけ見ればだいたいわかってしまう。
参照のための関数はrb_gv_get()
とrb_gvar_get()
である。
716 VALUE 717 rb_gv_get(name) 718 const char *name; 719 { 720 struct global_entry *entry; 721 722 entry = rb_global_entry(global_id(name)); 723 return rb_gvar_get(entry); 724 } 649 VALUE 650 rb_gvar_get(entry) 651 struct global_entry *entry; 652 { 653 struct global_variable *var = entry->var; 654 return (*var->getter)(entry->id, var->data, var); 655 } (variable.c)
実質的な内容はrb_global_entry()
にたらいまわしされているが、やろうと
していることはなんとなくわかる。global_id()
はchar*
をID
に変換して、
ついでにそれがグローバル変数のID
かチェックしてくれる関数だろう。
(*var->getter)(...)
はもちろん関数ポインタvar->getter
を使った
関数呼び出しである。p
が関数ポインタとすると(*p)(arg)
でその
関数を呼び出せるのだった。
なんにしても本筋はrb_global_entry()
だ。
351 struct global_entry* 352 rb_global_entry(id) 353 ID id; 354 { 355 struct global_entry *entry; 356 357 if (!st_lookup(rb_global_tbl, id, &entry)) { 358 struct global_variable *var; 359 entry = ALLOC(struct global_entry); 360 st_add_direct(rb_global_tbl, id, entry); 361 var = ALLOC(struct global_variable); 362 entry->id = id; 363 entry->var = var; 364 var->counter = 1; 365 var->data = 0; 366 var->getter = undef_getter; 367 var->setter = undef_setter; 368 var->marker = undef_marker; 369 370 var->block_trace = 0; 371 var->trace = 0; 372 } 373 return entry; 374 } (variable.c)
本処理は冒頭のst_lookup()
だけで、あとのコードは新規エントリを作って
(そして登録して)いる。つまり初めて参照したときに自動的にエントリが
作られるわけだから、rb_global_entry()
はNULL
を返すということがない。
こういう仕組みになっているのは主に高速化のためである。パースの時点でグ
ローバル変数を見付けたら対応するstruct global_entry
を取得してしまい、
変数を参照するときは(rb_gv_get()
を使って)エントリから値を取るだけ、
というふうにするのだ。
さてコードの続きをもう少し。var->getter
などにundef_xxxx
という
関数ポインタがセットされている。undef
、つまりグローバル変数が
まだ未定義(undefined)である状態のsetter/getter/marker
、という
意味だろう。
undef_getter()
では警告だけ出してnil
を返す。グローバル変数は定義せずに
参照できるからだ。undef_setter()
がちょっと面白いので見てみよう。
385 static void 386 undef_setter(val, id, data, var) 387 VALUE val; 388 ID id; 389 void *data; 390 struct global_variable *var; 391 { 392 var->getter = val_getter; 393 var->setter = val_setter; 394 var->marker = val_marker; 395 396 var->data = (void*)val; 397 } (variable.c)
val_getter()
は値をentry->data
から取って返すだけ、
val_setter()
は値をentry->data
に入れるだけの関数だ。
このようにハンドラを登録しなおすことで未定義という
特別な状態を特別扱いすることなく処理できる(図2)。
うまいやりかただ。
図2: グローバル変数の代入・参照
御意見・御感想・誤殖の指摘などは 青木峰郎 <aamine@loveruby.net> までお願いします。
『Rubyソースコード完全解説』 はインプレスダイレクトで御予約・御購入いただけます (書籍紹介ページへ飛びます)。
Copyright (c) 2002-2004 Minero Aoki, All rights reserved.