シャミロスでつらい。
2期が来るまで現実を忘れるためにkey関数のソースを掘ってみようと思ったのだ。
これがkey関数のソースです。
PHP_FUNCTION(key)
{
HashTable *array;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY_OR_OBJECT_HT(array)
ZEND_PARSE_PARAMETERS_END();
zend_hash_get_current_key_zval(array, return_value);
}
なんだ、凄く短いですね。
これなら楽勝じゃないですか。
しかし幾つかマクロが使われているので、ちょっと中身を調べてみましょう。
ZEND_PARSE_PARAMETERS_STARTの実装。
#define ZEND_PARSE_PARAMETERS_START(min_num_args, max_num_args) \
ZEND_PARSE_PARAMETERS_START_EX(0, min_num_args, max_num_args)
1行のたらい回し。
ZEND_PARSE_PARAMETERS_START_EXの実装はこんな。
#define ZEND_PARSE_PARAMETERS_START_EX(flags, min_num_args, max_num_args) do { \
const int _flags = (flags); \
int _min_num_args = (min_num_args); \
int _max_num_args = (max_num_args); \
int _num_args = EX_NUM_ARGS(); \
int _i; \
zval *_real_arg, *_arg = NULL; \
zend_expected_type _expected_type = Z_EXPECTED_LONG; \
char *_error = NULL; \
zend_bool _dummy; \
zend_bool _optional = 0; \
int error_code = ZPP_ERROR_OK; \
((void)_i); \
((void)_real_arg); \
((void)_arg); \
((void)_expected_type); \
((void)_error); \
((void)_dummy); \
((void)_optional); \
\
do { \
if (UNEXPECTED(_num_args < _min_num_args) || \
(UNEXPECTED(_num_args > _max_num_args) && \
EXPECTED(_max_num_args >= 0))) { \
if (!(_flags & ZEND_PARSE_PARAMS_QUIET)) { \
if (_flags & ZEND_PARSE_PARAMS_THROW) { \
zend_wrong_parameters_count_exception(_min_num_args, _max_num_args); \
} else { \
zend_wrong_parameters_count_error(_min_num_args, _max_num_args); \
} \
} \
error_code = ZPP_ERROR_FAILURE; \
break; \
} \
_i = 0; \
_real_arg = ZEND_CALL_ARG(execute_data, 0);
なんだこれ。
尤も、このあたりは定型文なので、毎回わざわざ掘り返す必要はありません。
ZEND_PARSE_PARAMETERS_STARTとZEND_PARSE_PARAMETERS_ENDで挟まれたところにZ_PARAM_XXXと書いておけば引数を受け取れるというイメージでよいです。
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY_OR_OBJECT_HT(array)
ZEND_PARSE_PARAMETERS_END();
key関数は配列もしくはオブジェクト型の引数をひとつ受け取ります。
はい、マニュアルには引数は配列って書いてあるのですが、実際は何の問題もなくオブジェクトを渡せます。
まあ今回はそこが焦点ではないのでどうでもいいとして、要するにこの3行は、単に引数を受け取る処理です。
ということでこの関数は、実質zend_hash_get_current_key_zval(array, return_value);
の1行だけということになります。
zend_hash_get_current_key_zvalは小文字だからわかりにくいですが、これも実はマクロです。
zend_hash_get_current_key_zvalの実装。
#define zend_hash_get_current_key_zval(ht, key) \
zend_hash_get_current_key_zval_ex(ht, key, &(ht)->nInternalPointer)
PHPには1行だけのマクロが大量にあるのですが、これは何の意味があるのだろう。
zend_hash_get_current_key_zval
はkey関数しか使ってないんだけど、これ直接zend_hash_get_current_key_zval_ex
を呼ぶようにしたらいけないんですかね?(わかってない)
zend_hash_get_current_key_zval_exでようやく処理部分に到達しました。
ZEND_API void ZEND_FASTCALL zend_hash_get_current_key_zval_ex(const HashTable *ht, zval *key, HashPosition *pos)
{
uint32_t idx;
Bucket *p;
IS_CONSISTENT(ht);
idx = _zend_hash_get_valid_pos(ht, *pos);
if (idx >= ht->nNumUsed) {
ZVAL_NULL(key);
} else {
p = ht->arData + idx;
if (p->key) {
ZVAL_STR_COPY(key, p->key);
} else {
ZVAL_LONG(key, p->h);
}
}
}
ここまで来れば詳細を読まなくても、_zend_hash_get_valid_pos
で現在地を取ってきて、範囲外ならNULLを返す、キーが文字列ならstring値を、数値ならlong値を返す、ってだいたいわかりますね。
しかし脈絡なく出てくるIS_CONSISTENTってなんだ?
これはZEND_DEBUGであれば_zend_is_inconsistent(a, __FILE__, __LINE__);
で、そうでなければ空っぽになるようです。
ZEND_DEBUGはコンパイルオプション--enable-debug
を指定してコンパイルしたときに有効になる値です。
つまり完全にデバッグ用途みたいなので今回はスルーしましょう。
さて見て見ぬふりをしましたが、ZEND_FASTCALLやらZVAL_STR_COPYやらZVAL_LONGやらも全部マクロです。
その中でもさらにマクロが使われていてさらにその中でも…の無限連鎖で、どこまで追っても終わりません。
関数ひとつ見るだけで力尽きました。
こんなのをさくさく書いてる人たちはいったいどうやってPHPを理解してるのですか?
わかりません><教えてください><