Press "Enter" to skip to content

一个想当然造成的错误(赋值语句的返回值)

一个想当然造成的错误.
需求是这样的, 我需要给一个二进制流加入一个签名串. 那么首先, 理所当然我的写了一个签名函数, 考虑到要判断签名操作是否成功, 所以我采用了传引用:

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判断条件). 想象赋值也和函数调用一样, 有返回值, 就容易理解多了.

14 Comments

  1. domain
    domain October 22, 2014

    If you want to improve your familiarity just keep visiting
    this site and be updated with the most recent news
    update posted here.

  2. xishuai
    xishuai January 13, 2012

    前两天还在和同事讨论这个问题,今天算是看明白了点,多谢。

  3. shiny
    shiny January 20, 2010

    @erway
    同期待laruence
    如果写了必买

  4. erway
    erway January 16, 2010

    博主:你好!
    我是机械工业出版社华章公司(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)?
    期待与你取得联系,期待与你合作。

  5. shinezhou
    shinezhou January 7, 2010

    $a = $b这个表达式的结果竟然不是左值$a…
    让人郁闷了~

  6. 小蔡
    小蔡 January 5, 2010

    感觉还是有点乱,晚上回去看看源码

  7. 雪候鸟
    雪候鸟 January 5, 2010

    @小蔡 b是a的引用, 调用的时候要传值, 所以就产生分离了啊.

  8. 小蔡
    小蔡 January 5, 2010

    $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呢?

  9. 雪候鸟
    雪候鸟 January 3, 2010

    @PeterYu 在PHP 5.3.0 (cli)下, 关于这个问题的语法分析逻辑以及complie.c中的zend_do_pass_param逻辑和PHP5.2.x一样,经过我测试, 结论和本文的一致. 另外我没听过”非明示变量值”这个名词, 是指匿名变量(变量直接量)么?

  10. PeterYu
    PeterYu January 3, 2010

    5.3开始,直接禁止了引用一个”非明示的变量值”.
    :-),你可以试试,

  11. Anders
    Anders January 3, 2010

    有些 trick 样的语句还是少用的好,呵呵。

  12. LANYE
    LANYE January 3, 2010

    ..嘿嘿

Comments are closed.