func_get_args系の関数の変な動きから、EG(argument_stack)を中途半端に眺める (続き)
(func_get_args系の関数とは、func_get_args / func_get_arg / func_num_args の3つです。)
前回の続き。
ちょっと間違えてた。くまさんから指摘。
関数の引数パラメータとしてfunc_get_args()を使っても、引数の1個目であればきちんと動く。
<?php function hoge() { var_dump(join(',', func_get_args())); } hoge(1, 2, 3); ?>
は、"PHP Fatal error: func_get_args(): Can't be used as a function parameter" とエラーが出て動かないわけなのですが、
<?php function hoge() { var_dump(join(func_get_args(), ',')); } hoge(1, 2, 3); ?>
は動く。すなわち、関数の引数としてfunc_get_args()を使うとき、第1引数ならOKということ。
join(',', func_get_args())
だと、スタックに ','
が積まれるが、join(func_get_args(), ',')
だと、先頭の引数だから積まれていないから。そして、join (というか、implode)は、歴史的な理由により、引数をどちらの順番でも受けつけることが可能であるから。バッドノウハウすぎるのでやっちゃだめですが、こんなの。
結局、argument_stackに想定外に載っていると動かないのですが、関数の先頭の引数だと自分の前には積む人がいないからOKということ。引数の処理に入るときに、なにかpaddingでも詰めるのかと思ったら、そういうことはしていないようで。
どちらにしろdebug_backtrace()は正しく動く
debug_backtraceの例も悪かったので、新しいのを。
implode()だと、多次元配列を食わせられないので、var_dump()にしています。var_dumpは引数を複数指定することもできて、その場合は、1個ずつvar_dumpを呼び出すのと同じ結果が得られます。
まずは、func_get_args()の場合。
<?php function hoge() { var_dump(func_get_args(), 2); var_dump(1, func_get_args()); } hoge(1, 2, 3); ?>
これは以下の結果を返します。
array(3) { [0]=> int(1) [1]=> int(2) [2]=> int(3) } int(2) PHP Fatal error: func_get_args(): Can't be used as a function parameter
上のvar_dumpが成功して、下のvar_dumpで失敗していることがわかります。
これを、debug_backtraceに置き換えた以下のコードでは、
<?php function hoge() { var_dump(debug_backtrace(), 2); var_dump(1, debug_backtrace()); } hoge(1, 2, 3); ?>
普通に動いて、
array(1) { [0]=> array(4) { ["file"]=> string(23) "/home/iogi/tmp/test.php" ["line"]=> int(7) ["function"]=> string(4) "hoge" ["args"]=> array(3) { [0]=> &int(1) [1]=> &int(2) [2]=> &int(3) } } } int(2) int(1) array(1) { [0]=> array(4) { ["file"]=> string(23) "/home/iogi/tmp/test.php" ["line"]=> int(7) ["function"]=> string(4) "hoge" ["args"]=> array(3) { [0]=> &int(1) [1]=> &int(2) [2]=> &int(3) } } }
となる。正しく引数は取れている。
vld (Vulcan Logic Disassembler)を使ってみよう
そして、くまさんが vld で動きを確認していた。これでみると、引数スタックの可視化はできないものの、若干ZendEngineの動きがわかりやすくなります。
vldとは
The Vulcan Logic Disassembler hooks into the Zend Engine and dumps all the opcodes (execution units) of a script. It was written as as a beginning of an encoder, but I never got the time for that. It can be used to see what is going on in the Zend Engine.
Projects — Derick Rethans
簡単な実行例
まずは、どういうものかを理解するために、シンプルなもので見てみたいと思います。
1 <?php 2 $a = "this is"; 3 $b = $a; 4 $c = $a; 5 $c = 42; 6 unset($b); 7 unset($c); 8 ?>
これを実行すると、
$ php -dvld.active=1 test.php
このような結果が得られます。
Branch analysis from position: 0 Return found function name: (null) number of ops: 8 compiled vars: !0 = $a, !1 = $b, !2 = $c line # op fetch ext return operands ------------------------------------------------------------------------------- 2 0 ASSIGN !0, 'this+is' 3 1 ASSIGN !1, !0 4 2 ASSIGN !2, !0 5 3 ASSIGN !2, 42 6 4 UNSET_VAR 'b' 7 5 UNSET_VAR 'c' 9 6 RETURN 1 7* ZEND_HANDLE_EXCEPTION
読み方は、例えば、ASSIGN !0, 'this+is'
で、!0 は compiled vars のところに書いてあるように $a なので、$a に 'this+is' を割り当て、となります。(なぜ this+is とスペースが+に変換されるのかは不明)
ここに示しているコードは、先日シカゴで開催された php|tek での Derick Rethansのプレゼン資料、PHP Secrets (PDF)の付録、27ページの図に対応してます。参考までに。
vldでfunc_get_argsの場合を見てみる
まずは、成功パターン
1 <?php 2 function hoge() 3 { 4 echo var_dump(func_get_args("b"), "c", "d"); 5 } 6 hoge("a"); 7 ?>
分かりやすくなるようにわざとfunc_get_argsに不要な引数 "b" を渡しています。
この場合は、以下のような結果が得られます。
Branch analysis from position: 0 Return found filename: /home/iogi/tmp/test.php function name: (null) number of ops: 5 compiled vars: none line # op fetch ext return operands ------------------------------------------------------------------------------- 2 0 NOP 6 1 SEND_VAL 'a' 2 DO_FCALL 1 'hoge' 8 3 RETURN 1 4* ZEND_HANDLE_EXCEPTION Function hoge: Branch analysis from position: 0 Return found filename: /home/iogi/tmp/test.php function name: hoge number of ops: 9 compiled vars: none line # op fetch ext return operands ------------------------------------------------------------------------------- 4 0 SEND_VAL 'b' 1 DO_FCALL 1 'func_get_args' 2 SEND_VAR_NO_REF $0 3 SEND_VAL 'c' 4 SEND_VAL 'd' 5 DO_FCALL 3 'var_dump' 6 ECHO $1 5 7 RETURN null 8* ZEND_HANDLE_EXCEPTION End of function hoge.
ユーザ関数を呼び出すと、それは別に記述されるので、このように2つ出てきます。上のものについては、'a' を積んで(#1) hoge という関数を呼んでいる(#2)ことが分かるとおもいます。
そして、関数hoge()側では、まず "b" を詰めて(#0)、func_get_args を呼ぶ(#1)。関数に入ってからこの時点までは、自身の引数(b)以外にスタックには何も積まれてないのでOK。
失敗パターン
こんどは、func_get_argsより前に引数 "x", "y" をもってきました。
1 <?php 2 function hoge() 3 { 4 echo var_dump("x", "y", func_get_args("b")); 5 } 6 hoge("a"); 7 ?>
で、こうなる。
line # op fetch ext return operands ------------------------------------------------------------------------------- 4 0 SEND_VAL 'x' 1 SEND_VAL 'y' 2 SEND_VAL 'b' 3 DO_FCALL 1 'func_get_args' 4 SEND_VAR_NO_REF $0 5 DO_FCALL 3 'var_dump' 6 ECHO $1 5 7 RETURN null 8* ZEND_HANDLE_EXCEPTION
引数の "x", "y" が先にセットされて(#0, #1)、その後にfunc_get_argsの引数詰め(#2)、関数呼び出し(#3)が行われています。これだと、スタックに何か積まれていてNG。
まとめ
- やっぱり、func_get_argsの実装はいけてない。
- debug_backtraceのようにちゃんと書かないのは、理由があるのだろうか。
- vldはおもしろいけど、普段役に立つことはない
- Opcodeの話なんてのも書いてるAdvanced Php Programming本は素敵。
- 作者: George Schlossnagle
- 出版社/メーカー: Addison-Wesley Professional
- 発売日: 2020/02/19
- メディア: ペーパーバック
- クリック: 4回
- この商品を含むブログ (3件) を見る