(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件) を見る