変数がどのように格納され処理されているのかについてきちんと理解することは、
ハッカー
になるための第一歩です。エンジンは、構造体のさまざまな
フィールドにアクセスするための統一された直感的なマクロを提供することで、
それがどのような型の変数であっても、その概念の複雑さを隠蔽しようとします。
この章の中身に沿って学習していけば、ハッカー
は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 の参照カウントを変更せずに、dst を
src のコピーにする |
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_ctor
とzval_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 のフラグの操作等についての 理解が進んだはずです。