- 本文地址: https://www.laruence.com/2010/01/03/1225.html
- 转载请注明出处
一个想当然造成的错误.
需求是这样的, 我需要给一个二进制流加入一个签名串. 那么首先, 理所当然我的写了一个签名函数, 考虑到要判断签名操作是否成功, 所以我采用了传引用:
function sign(&$carrier, $fingerprint) { if (NULL === $fingerprint) { return FALSE; } //加入签名 $carrier = 签名逻辑. return TRUE; }
接下来, 考虑到, 如果签名失败, 那还是使用原来的字符串做为结果, 所以, 我想当然的写下了如下的代码:
$bin_str = **************; $success = sign($after_signed = $bin_str, "laruence's fingerprint"); if ($success) { //使用$after_signed } else { //使用$bin_str }
考虑到简单变量是传值引用, 在bin_str赋值给after_signed以后, 我以为after_signed的引用会传递给carrier..
但, 结果是, after_signed并没有被加入签名串...
那为什么会错呢?
经过对opcode的分析, 发现原来, PHP在做二元赋值运算的时候, 返回值并不是左值,而是一个临时变量. 也就是说对于:
$a = $b;
它的返回值, 并不是$a, 而是一个临时变量, 假设是$2.
所以传递给sign函数的, 并不是$a的引用, 而是$2;
以下内容是对源码的分析范畴, 如果只是想知道结论的, 略过如下也可:
现在, 结合之前的文章深入理解PHP原理之变量分离/引用(Variables Separation)中介绍过的相关知识, 来做个详细是分析:
$a = "laruence"; function ch(&$p) { debug_zval_dump($p); $p = 'eve'; debug_zval_dump($p); } debug_zval_dump($a); ch($a); ch($b=$a);
运行结果是:
//初始的a string(8) "laruence" refcount(2) //第一次调用change中的p string(8) "laruence" refcount(1) string(3) "eve" refcount(1) //第二次赋值结果调用change中的a string(3) "eve" refcount(3) string(3) "eve" refcount(2)
首先, 最初的时候, $a的引用是2, 这是因为简单变量传值, 所以传给debug_zval_dump后, $a有一个copy的引用计数.
当第一次调用的时候, 我们直接传$a, 因为change函数的参数申明是传引用, 在change中调用debug_zval_dump时, $p是一个引用. 而要传值调用, 所以产生了一次分离, 得到计数为1.
重点看下第二次调用, 此时change中第一次debug_zval_dump的引用计数是3. 怎么会是3呢?.
此处要是3. 那也就是说$p是一个引用计数为2的非引用变量.
可是, 明明不是申明了change接受引用参数么?
没办法, 查看源代码. 在语法分析时刻, 看出了差别:
non_empty_function_call_parameter_list: expr_without_variable { Z_LVAL($$.u.constant) = 1; zend_do_pass_param(&$1, ZEND_SEND_VAL, Z_LVAL($$.u.constant) TSRMLS_CC); } | variable { Z_LVAL($$.u.constant) = 1; zend_do_pass_param(&$1, ZEND_SEND_VAR, Z_LVAL($$.u.constant) TSRMLS_CC); }
对于第二次调用, 它将满足的是第一条规约规则, 也就是传给zend_do_pass_param的第二个参数是ZEND_SEND_VAL...
继续追查zend_do_pass_param:
if (op == ZEND_SEND_VAR && zend_is_function_or_method_call(param)) { //是否是函数或者方法的返回值 /* Method call */ op = ZEND_SEND_VAR_NO_REF; send_function = ZEND_ARG_SEND_FUNCTION; } else if (op == ZEND_SEND_VAL && (param->op_type & (IS_VAR|IS_CV))) { op = ZEND_SEND_VAR_NO_REF;//在这里被改成了NO_REF }
也就是, 因为第二次调用的时候传递给change的变量, 是一个"直接量", 并且属于IS_VAR, 所以PHP会把传递方式改变为传值.
所以在change中第一次debug_zval_dump的时候, 引用计数就是3了. ~
其实, 分析到这里的时候, 我们可以类比函数的返回值做为参数的情形(第一个if判断条件). 想象赋值也和函数调用一样, 有返回值, 就容易理解多了.
C’est super cool !
If you want to improve your familiarity just keep visiting
this site and be updated with the most recent news
update posted here.
前两天还在和同事讨论这个问题,今天算是看明白了点,多谢。
[…] 本文地址: http://www.laruence.com/2010/01/03/1225.html […]
@erway
同期待laruence
如果写了必买
博主:你好!
我是机械工业出版社华章公司(www.hzbook.com,中国IT出版4强)的高级策划编辑杨福川,冒昧打扰了,还请见谅。
今天无意中从一位朋友的博客中链接到了你的博客中,顿时眼前一亮,原来又发现了一位高人。我一直想策划一本关于PHP源码剖析的书,苦于找不到作者,没想到今天无意中发现了一位大牛,庆幸哉!我仔细拜读了你撰写的关于PHP的文章,看得出来你对PHP颇有研究,似乎对PHP源码情有独钟,何不系统性地来分析一下PHP的源码呢?这不但能造福广大PHPer,也许能成为你人生的一个转折点,哈哈。
我准备策划一系列关于源码分析的书,目前已经出版和策划的有:
1. 《Spring技术内幕——深入解析Spring架构与设计原理》,最近刚出版,当当网2天就卖断货。
2. 《Struts技术内幕——深入解析Struts架构与设计原理》,正在策划中。
3. 《JVM技术内幕》,正在策划中。
4. 《MySQL技术内幕》,正在策划中。
《PHP技术内幕》也是我策划的这套书中非常重要的一本,一直还没有找到比较合适的朋友来写,今日认识了兄弟你,我想也许我找到了这个选题的主人,不知道是否有机会与你合作。为了便于联系,能否交换一下联系方式(MSN:erwa_yang@live.cn;QQ:15693352)?
期待与你取得联系,期待与你合作。
$a = $b这个表达式的结果竟然不是左值$a…
让人郁闷了~
感觉还是有点乱,晚上回去看看源码
@小蔡 b是a的引用, 调用的时候要传值, 所以就产生分离了啊.
$a = ‘test’;
$b = & $a;
function ch(&$p) {
debug_zval_dump($p);
$p = ‘eve’;
debug_zval_dump($p);
}
debug_zval_dump($a);
ch($a);
ch($b=$a);
为什么这里输出的refcount全都是1呢?
@PeterYu 在PHP 5.3.0 (cli)下, 关于这个问题的语法分析逻辑以及complie.c中的zend_do_pass_param逻辑和PHP5.2.x一样,经过我测试, 结论和本文的一致. 另外我没听过”非明示变量值”这个名词, 是指匿名变量(变量直接量)么?
5.3开始,直接禁止了引用一个”非明示的变量值”.
:-),你可以试试,
有些 trick 样的语句还是少用的好,呵呵。
..嘿嘿