LoginSignup
8
4

More than 3 years have passed since last update.

わかりません><教えてください><

Posted at

シャミロスでつらい。

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を理解してるのですか?
わかりません><教えてください><

8
4
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
4