Quantcast
Channel: rana_kualuの記事 - Qiita
Viewing all articles
Browse latest Browse all 331

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

$
0
0

シャミロスでつらい。

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


Viewing all articles
Browse latest Browse all 331

Trending Articles