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()を見てみよう。
▼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 superstruct st_table *iv_tblstruct st_table *m_tblの三つだ。そこで考えるに、
struct RClassにあるようには見えないiv_tblに同居していたじゃあもしかして定数も……
rb_const_set()は定数に値を代入する関数である。
クラスklassの定数idに、valをセットする。
▼rb_const_set()
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()に委譲されている。
▼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()を無視する。すると残りはこれだけだ。
▼mod_av_set()(重要部分のみ)
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を参照する。
▼rb_const_get()
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のあたりはどうもモジュールを特別扱いしている
ようだ。これもとりあえず消してしまおう。するとここまで減らせる。
▼rb_const_get(簡約版)
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に書き込んで返す。
▼top_const_get()
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()である。
▼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()だ。
▼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()がちょっと面白いので見てみよう。
▼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.