C のような言語における "グローバル" 変数とは、 特別な宣言をしなくてもすべての関数からアクセスできる変数のことです。 この昔ながらのグローバル変数には、いくつかの弱点があります。
PHP 拡張モジュールのグローバル変数は、どちらかというと "extension state (拡張モジュールの状態)" と呼んだほうが適切でしょう。 ほとんどのモジュールは、関数コールの間に自分が何をしているのかを覚えておく必要があるからです。 "counter" 拡張モジュールは、その最たる例でしょう。 基本インターフェイスでは、カウンタの値を永続化させています。 Zend や PHP にあまりなじみのないプログラマのみなさんは、counter.c で値を保存するときにこんな風にしてしまいがちです。
例1 カウンタの基本インターフェイスで値を保存するときの間違った方法
/* ... */ static long basic_counter_value; /* ... */ PHP_FUNCTION(counter_get) { RETURN_LONG(basic_counter_value); }
上っ面だけ見ればこれは正しそうに見えるでしょうし、 実際のところ単純なテストでは正しく動作します。 しかし、複数の PHP が同一スレッドで動作することもよくあります。 そんな場合は counter モジュールの複数のインスタンスが存在することになります。 そして複数のスレッドが同じカウンタの値を共有することになりますが、 これが望ましい結果でないことは明らかです。 さらに別の問題もあります。別の拡張モジュールが ある日たまたま同じ名前のグローバル変数を持つことになったとしましょう。 C のスコープの規則では、この場合コンパイルが失敗してしまう可能性があります。 さらに悪いことに、実行時エラーとなる可能性もあります。 少し頭を使う必要があるでしょう。 そこで Zend では、スレッドセーフなモジュール単位のグローバル変数をサポートしています。
そのモジュールで使うグローバル変数がひとつであろうと大量であろうと、
それを構造体の中で定義したうえで構造体を宣言しなければなりません。
モジュール間での名前の衝突を防いでそれを支援するマクロが
ZEND_BEGIN_MODULE_GLOBALS() や
ZEND_END_MODULE_GLOBALS() そして
ZEND_DECLARE_MODULE_GLOBALS() です。
これらのマクロに渡すパラメータはモジュールの短い名前で、
counter モジュールの場合は "counter"
となります。
php_counter.h でのグローバル構造体の宣言の例を示します。
例2 counter モジュールのグローバル変数
ZEND_BEGIN_MODULE_GLOBALS(counter) long basic_counter_value; ZEND_END_MODULE_GLOBALS(counter)
そして、これが counter.c での宣言です。
例3 counter モジュールのグローバル構造体宣言
ZEND_DECLARE_MODULE_GLOBALS(counter)
先ほど説明したように、モジュール単位のグローバルは C の構造体の内部で宣言されており、その名前は Zend マクロで隠蔽されています。 構造体のメンバーにアクセスするための最もよい方法は、 これらのマクロを使用することです。 したがって、ほとんどすべてといっていいほどの拡張モジュールには、 ヘッダファイルのどこかに次のような宣言があります。
例4 モジュール単位のグローバルにアクセスするためのマクロ
#ifdef ZTS #define COUNTER_G(v) TSRMG(counter_globals_id, zend_counter_globals *, v) #else #define COUNTER_G(v) (counter_globals.v) #endif
注意: これは Zend API によって Zend マクロに一般化されるようになるかもしれません。 しかし PHP 5.3 の時点では (そして執筆時点では PHP 6 でも)、 まだそのようにはなっていません。 グローバルへのアクセサは、 ext_skel がヘッダに書き込みます。 開発者がそのマクロの名前を変更しようとしない限り、通常はそのままにしておきます。
注意:
COUNTER_G
は ext_skel がつけたマクロ名ですが、 この名前でなければならないというわけではありません。たとえばFOO
などに変更してもかまいません。
したがって、counter 拡張モジュールのコード内でグローバルにアクセスするには必ず
COUNTER_G
マクロを使用しなければなりません。
グローバルにアクセスする関数は、Zend マクロで宣言するか最後の引数を
TSRMLS_DC
にするか、
あるいはグローバルにアクセスする前に
TSRMLS_FETCH
マクロをコールするようにしなければなりません。
詳細は
TSRM のドキュメント
を参照ください。
これらすべてを考慮すると、 counter_get() の新しいバージョンはこのようになります。
例5 正しい方法での基本カウンタインターフェイスの値の保存
/* php_counter.h */ ZEND_BEGIN_MODULE_GLOBALS(counter) long basic_counter_value; ZEND_END_MODULE_GLOBALS(counter) #ifdef ZTS #define COUNTER_G(v) TSRMG(counter_globals_id, zend_counter_globals *, v) #else #define COUNTER_G(v) (counter_globals.v) #endif /* counter.c */ ZEND_DECLARE_MODULE_GLOBALS(counter) /* ... */ PHP_FUNCTION(counter_get) { RETURN_LONG(COUNTER_G(basic_counter_value)); }
これは正しい実装です。しかし、完全なものではありません。その理由は 拡張モジュールのライフサイクル で説明します。