func_get_args系の関数の変な動きから、EG(argument_stack)を中途半端に眺める
※この記事ではちと勘違いがあったので、次のエントリも一緒に読んでください。
くまさんからのリクエスト。
func_get_argsなどの関数の引数を受け取る関数は、任意の引数を取る関数などを書く際によく使います。これで、ありがちなのが、
<?php function hoge() { $content = join('', func_get_args()); } hoge(); ?>
をすると、
PHP Fatal error: func_get_args(): Can't be used as a function parameter
と警告でちゃうこと。
その理由として、PHPマニュアルには、
注意: この関数は、カレントスコープに依存してパラメータの詳細を決定しますので、関数パラメータとして使用することはできません。もし、この値を渡さなければならない場合、戻り値を変数に割り当て、その変数を渡してください。
PHP: func_get_args - Manual
と書いてある。
いやいや、カレントスコープに依存って、仮にも変数のスコープと同じなら
$a = 3; hoge( $a );
って、やれば、$aの値 (参照渡しであればその参照)がhogeに渡るわけで、これができないことになる。hoge( moge() );
とやるとスコープが違うとはどういうことだろう。
先に結論
変数スコープと、この関数が見に行くパラメータ引数のスコープは違うからさ、ってこと。どちらかというと、そういう仕様ですってことだな。
func_get_argsのコード
まずは、関数がどうなっているかを眺めてみます。
zend_builtin_functions.c (PHP5.2.3)
/* {{{ proto array func_get_args() Get an array of the arguments that were passed to the function */ ZEND_FUNCTION(func_get_args) { void **p; int arg_count; int i; p = EG(argument_stack).top_element-1-1; arg_count = (int)(zend_uintptr_t) *p; /* this is the amount of arguments passed to func_get_args(); */ p -= 1+arg_count; if (*p) { zend_error(E_ERROR, "func_get_args(): Can't be used as a function parameter"); } --p; if (p<EG(argument_stack).elements) { zend_error(E_WARNING, "func_get_args(): Called from the global scope - no function context"); RETURN_FALSE; } arg_count = (int)(zend_uintptr_t) *p; array_init(return_value); for (i=0; i<arg_count; i++) { zval *element; ALLOC_ZVAL(element); *element = **((zval **) (p-(arg_count-i))); zval_copy_ctor(element); INIT_PZVAL(element); zend_hash_next_index_insert(return_value->value.ht, &element, sizeof(zval *), NULL); } } /* }}} */
意外にすっきりしていますが、普通のExtensionと違うのは、EG(argument_stack)とか変なモノを触っているということでしょう。
ダブルポインタ操作のお勉強的なコードですね。。
EG(argument_stack)とは
EG(argument_stack) が指すのは、executor globals構造体内の zend_ptr_stack構造体という、汎用的なスタックです。スクリプト実行時に、関数やメソッドを呼び出すたびに、引数が積まれていくイメージです。
typedef struct _zend_ptr_stack { int top, max; void **elements; void **top_element; } zend_ptr_stack;
関数を呼び出したときに、このスタックに引数が積まれるイメージです。
関数パラメータ内で呼び出したときのハンドリング
void **p; int arg_count; // argument_stackの先頭要素-2 p = EG(argument_stack).top_element-1-1; // ここに引数の個数が入ってる arg_count = (int)(zend_uintptr_t) *p; /* this is the amount of arguments passed to func_get_args(); */ // 引数個+1デクリメント p -= 1+arg_count; if (*p) { zend_error(E_ERROR, "func_get_args(): Can't be used as a function parameter"); }
となっています。
ここでやってるのは、func_get_args()自体を呼び出す際にも引数を付けることができるワケで、その数の分だけデクリメントをしているという形。そして、その次のところに、何かフラグが入っている模様。
結局のところ、引数として呼び出そうとしたときは、何かしらスタックに積まれているので、一筋縄ではデータが取れないということでしょう。
絵
結局、スタックの積み方とかがよく分からないのですが、ポインタの動きを理解するために参考までに書いて見た絵を。PCで書くのうざいから手書き。
どっかにargument_stackの詰め方の説明ないのかしらん。
手抜きではないかという気もしなくもない。
いくら探しているところには無いとはいえ、スタックなのでさらに前に積んであるのです。
例えば、debug_backtraceは同じように関数の引数も表示してくれるのですが、
<?php function hoge() { $bt = debug_backtrace(); var_dump($bt); moge(debug_backtrace()); } function moge($param) { var_dump($param); } hoge("A"); ?>
として、関数内に普通に書いたパターンと、関数の引数に書いたパターンがある場合、
array(1) { [0]=> array(4) { ["file"]=> string(23) "/home/iogi/tmp/test.php" ["line"]=> int(13) ["function"]=> string(4) "hoge" ["args"]=> array(1) { [0]=> &string(1) "A" } } } array(1) { [0]=> array(4) { ["file"]=> string(23) "/home/iogi/tmp/test.php" ["line"]=> int(13) ["function"]=> string(4) "hoge" ["args"]=> array(1) { [0]=> &string(1) "A" } } }
と同じ結果が得られ、きちんと関数の引数のレベルを無視しているように見えます。
まとめ
- わかったこと
- 関数の引数から呼び出したら、エラーが出る理由
- どちらにしろ、イケてないこと
- わからないこと
- EG(argument_stack)へのデータ格納の仕様
こんなの調べても何にも役にたたないよ!
※この記事ではちと勘違いがあったので、次のエントリも一緒に読んでください。