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

本文通过一个实际的代码示例,探讨了PHP中引用传递的一个常见误解。在尝试对二进制流进行签名操作时,作者发现即使在函数中使用了引用传递,结果并未按预期更新。通过对PHP源码的分析,揭示了在二元赋值运算中,返回值并非左值,而是临时变量,导致引用并未正确传递。这一发现有助于开发者更深入地理解PHP中的变量引用和赋值行为。
摘要由CSDN通过智能技术生成

一个想当然造成的错误.

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值