変数入門

変数がどのように格納され処理されているのかについてきちんと理解することは、 ハッカーになるための第一歩です。エンジンは、構造体のさまざまな フィールドにアクセスするための統一された直感的なマクロを提供することで、 それがどのような型の変数であっても、その概念の複雑さを隠蔽しようとします。 この章の中身に沿って学習していけば、ハッカーはPHPの変数に関する 専門用語やその概念についての理解を深めることができるはずです。

注意:

PHPはコピーオンライトや参照カウント法を使う、動的で型の制約がゆるい言語です。

前述の内容もう少し正確に言うと、PHPは高水準言語であり、緩い型付けにより変数は エンジニアの好みに応じて暗黙に解釈され、実行時に必要な型に強制的に変換されます。 参照カウント法は、ある変数がユーザーのコードの中でもはや参照されなくなったことを エンジンが推測し、その変数に関連付けられた構造体を開放するという仕組みです。

PHP内部の変数は、すべてzvalと呼ばれるひとつの構造体で表現されています。

typedef struct _zval_struct {
    zvalue_value value;        /* 変数の値 */
    zend_uint refcount__gc;    /* 参照回数 */
    zend_uchar type;           /* 変数の型 */
    zend_uchar is_ref__gc;     /* リファレンスフラグ */
} zval;

zval_valueは、ひとつの変数が持ちうるすべての型を表現可能な共用体です。

typedef union _zvalue_value {
    long lval;                 /* long 値 */
    double dval;               /* double 値 */
    struct {                   
        char *val;
        int len;               /* 常に文字列用として設定されます */
    } str;                     /* 文字列(常に長さを持ちます) */
    HashTable *ht;             /* 配列 */
    zend_object_value obj;     /* オジュジェクト格納用ハンドルやハンドラを保持します */
} zvalue_value;

この構造体により、ある変数はいずれかひとつの型を持つことができ、そのデータは zval_value共用体の中の適切なフィールドによって表現されている ことがわかります。zval自体に型や参照回数を持ち、 またその変数がリファレンスかどうかを示すフラグも持っています。

ネイティブ型定数
定数 マッピング
IS_NULL 値がセットされていない
IS_LONG lval
IS_DOUBLE dval
IS_BOOL lval
IS_RESOURCE lval
IS_STRING str
IS_ARRAY ht
IS_OBJECT obj

注意:

上記の他にも、定数の配列や callable オブジェクトといった内部的な型を あらわす定数があるのですが、それらの利用法についてはこのドキュメントでは扱いません。

エンジンが公開しているマクロのうち、zval値で扱えるものを 以下の表に示します。

アクセサ・マクロ
プロトタイプ アクセス 説明
zend_uchar Z_TYPE(zval zv) type valueの型を返す
long Z_LVAL(zval zv) value.lval  
zend_bool Z_BVAL(zval zv) value.lval long のvalueを zend_bool にキャスト
double Z_DVAL(zval zv) value.dval  
long Z_RESVAL(zval zv) value.lval リソース一覧におけるvalueの識別子を返す
char* Z_STRVAL(zval zv) value.str.val 文字列のvalueを返す
int Z_STRLEN(zval zv) value.str.len 文字列valueの文字数を返す
HashTable* Z_ARRVAL(zval zv) value.ht ハッシュテーブル(配列)のvalueを返す
zend_object_value Z_OBJVAL(zval zv) value.obj オブジェクトのvalueを返す
uint Z_OBJ_HANDLE(zval zv) value.obj.handle オブジェクトvalueのオブジェクトハンドルを返す
zend_object_handlers* Z_OBJ_HT_P(zval zv) value.obj.handlers オブジェクトvalueのハンドラテーブルを返す
zend_class_entry* Z_OBJCE(zval zv) value.obj オブジェクトvalueのクラスエントリを返す
HashTable* Z_OBJPROP(zval zv) value.obj オブジェクトvalueのプロパティを返す
HashTable* Z_OBJPROP(zval zv) value.obj オブジェクトvalueのプロパティを返す
HashTable* Z_OBJDEBUG(zval zv) value.obj オブジェクトに get_debug_info ハンドラがセットされている 場合はそれが呼ばれ、そうでなければ Z_OBJPROP が呼ばれる

参照カウント法の原理の章をチェックして、 参照カウント法や参照がどのような仕組みで動いているのかを調べてみてください。

参照カウントの操作
プロトタイプ 説明
zend_uint Z_REFCOUNT(zval zv) valueの参照カウントを返す
zend_uint Z_SET_REFCOUNT(zval zv) valueの参照カウントをセットしてそれを返す
zend_uint Z_ADDREF(zval zv) valueの参照カウントを事前インクリメントしてそれを返す
zend_uint Z_DELREF(zval zv) valueの参照カウントを事前デクリメントしてそれを返す
zend_bool Z_ISREF(zval zv) zval が参照かどうかを返す
void Z_UNSET_ISREF(zval zv) is_ref__gc を 0 にする
void Z_SET_ISREF(zval zv) is_ref__gc を 1 にする
void Z_SET_ISREF_TO(zval zv, zend_uchar to) is_ref__gc をtoにする

注意:

前述の Z_* マクロはどれも1個の zval を受け取りますが、これらに _P サフィックス がついた、たとえばzend_uchar Z_TYPE_P(zval* pzv)のようなマクロも 定義されていて、これらはすべて zval へのポインタを受け取ります。さらに、たとえば zend_uchar Z_TYPE_PP(zval** ppzv)のように _PP サフィックスがついた ものもあり、これらは zval へのポインタのポインタを受け取ります。

生成、破壊、分割、コピー
プロトタイプ 説明
ALLOC_ZVAL(zval* pzval) pzvalを emalloc する
ALLOC_INIT_ZVAL(zval* pzval) pzvalを emalloc し、pzvalは初期化のために NULL として型付けられた zval を指すようにする
MAKE_STD_ZVAL(zval* pzval) pzvalを emalloc し、参照カウントを 1にする
ZVAL_COPY_VALUE(zval* dst, zval* src) srcの値と型をdstの値と型としてセットする
INIT_PZVAL_COPY(zval* dst, zval*dst) ZVAL_COPY_VALUE を実行し、dstの参照カウントを 1 にし、 is_ref__gc を0にする
SEPARATE_ZVAL(zval** ppzval) ppzvalの参照カウントが 1 より大きい場合、新たに emalloc して zval の中身をコピーし、zval と同じ型で同じ値にした場所を *ppzvalが指すようにする
SEPARATE_ZVAL_IF_NOT_REF(zval** ppzval) *ppzvalが参照ではない場合、ppzvalに対して SEPARATE_ZVAL を行う
SEPARATE_ZVAL_TO_MAKE_IS_REF(zval** ppzval) *ppzvalが参照ではない場合、ppzvalに対して SEPARATE_ZVAL と Z_SET_ISREF_PP を行う
COPY_PZVAL_TO_ZVAL(zval dst, zval** src) srcの参照カウントを変更せずに、dstsrcのコピーにする
MAKE_COPY_ZVAL(zval** src, zval* dst) INIT_PZVAL_COPY を行い、新しい zval に対して zval_copy_ctor する
void zval_copy_ctor(zval** pzval) 参照カウントをメンテナンスする。エンジン全体を通して広く使われる。
void zval_ptr_dtor(zval* pzval) 変数の参照カウントをデクリメントする。 参照カウントが 0 になったら変数は破壊される。
FREE_ZVAL(zval* pzval) pzvalを efree する

注意:

オブジェクトとリソースは、それぞれの構造体の一部として参照カウントを 持っています。これらについて zval_ptr_dtor が呼ばれると、それぞれにあった del_ref が実行されます。詳細はオブジェクトの扱いとリソースの扱いを参照してください。

さらにハッカーが知っておくべき機能をふたつだけ挙げるとすれば、 それはzval_copy_ctorzval_ptr_dtorでしょう。これらは エンジンにおける参照カウントメカニズムの基本です。重要なことは、通常の環境で zval_copy_ctorが呼ばれても、実際には何も起こらないことです。 これは単に参照カウントを増やしているだけです。同様に、zval_ptr_dtor が本当に変数を破壊するのは、それを参照するものがなくなって、参照カウントが 0 になった時だけです。

PHP 自体は弱い型付けしか行いませんが、エンジンが変数の型を別の型にするための API 関数を提供しています。

型の変換
プロトタイプ
void convert_to_long(zval* pzval)
void convert_to_double(zval* pzval)
void convert_to_long_base(zval* pzval, int base)
void convert_to_null(zval* pzval)
void convert_to_boolean(zval* pzval)
void convert_to_array(zval* pzval)
void convert_to_object(zval* pzval)
void convert_object_to_type(zval* pzval, convert_func_t converter)

注意:

convert_func_t 関数には(void) (zval* pzval) というプロトタイプが必要です。

ここまで読んでもらえたので、あなたはネイティブからエンジンまでの型、 型の検出とzval 値の読み取り方法、参照カウントや zval のフラグの操作等についての 理解が進んだはずです。