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