技术头条 - 一个快速在微博传播文章的方式     搜索本站
您现在的位置首页 --> PHP --> 一个想当然造成的错误(函数引用参数的一个问题)

一个想当然造成的错误(函数引用参数的一个问题)

浏览:2361次  出处信息

一个想当然造成的错误.

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

  1. function sign(&$carrier, $fingerprint) {
  2.      if (NULL === $fingerprint) {
  3.           return FALSE;
  4.      }
  5.      //加入签名
  6.      $carrier = 签名逻辑.
  7.      return TRUE;

接下来, 考虑到, 如果签名失败, 那还是使用原来的字符串做为结果, 所以, 我想当然的写下了如下的代码:

  1. $bin_str = **************;
  2.  
  3. $success = sign($after_signed = $bin_str, "laruence's fingerprint");
  4. if ($success) {
  5.      //使用$after_signed
  6. } else {
  7.      //使用$bin_str

考虑到简单变量是传值引用, 在bin_str赋值给after_signed以后, 我以为after_signed的引用会传递给carrier..
但, 结果是, after_signed并没有被加入签名串…

那为什么会错呢?

经过对opcode的分析, 发现原来, PHP在做二元赋值运算的时候, 返回值并不是左值,而是一个临时变量. 也就是说对于:

    它的返回值, 并不是$a, 而是一个临时变量, 假设是$2.
    所以传递给sign函数的, 并不是$a的引用, 而是$2;

    以下内容是对源码的分析范畴, 如果只是想知道结论的, 略过如下也可:

    现在, 结合之前的文章深入理解PHP原理之变量分离/引用(Variables Separation)中介绍过的相关知识, 来做个详细是分析:

    1. $a = "laruence";
    2. function ch(&$p) {
    3.     debug_zval_dump($p);
    4.     $p = 'eve';
    5.     debug_zval_dump($p);
    6. }
    7.  
    8. debug_zval_dump($a);
    9. ch($a);

    运行结果是:

    1. //初始的a
    2. string(8) "laruence" refcount(2)
    3. //第一次调用change中的p
    4. string(8) "laruence" refcount(1)
    5. string(3) "eve" refcount(1)
    6. //第二次赋值结果调用change中的a
    7. string(3) "eve" refcount(3)

    首先, 最初的时候, $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接受引用参数么?

    没办法, 查看源代码. 在语法分析时刻, 看出了差别:

    1. non_empty_function_call_parameter_list:
    2.         expr_without_variable
    3.              { Z_LVAL($$.u.constant) = 1;
    4.                     zend_do_pass_param(&$1, ZEND_SEND_VAL, Z_LVAL($$.u.constant) TSRMLS_CC); }
    5.     | variable
    6.                { Z_LVAL($$.u.constant) = 1;

    对于第二次调用, 它将满足的是第一条规约规则, 也就是传给zend_do_pass_param的第二个参数是ZEND_SEND_VAL…

    继续追查zend_do_pass_param:

    1. if (op == ZEND_SEND_VAR && zend_is_function_or_method_call(param)) {
    2.      //是否是函数或者方法的返回值
    3.      /* Method call */
    4.      op = ZEND_SEND_VAR_NO_REF;
    5.      send_function = ZEND_ARG_SEND_FUNCTION;
    6. } else if (op == ZEND_SEND_VAL && (param->op_type & (IS_VAR|IS_CV))) {
    7.      op = ZEND_SEND_VAR_NO_REF;//在这里被改成了NO_REF

    也就是, 因为第二次调用的时候传递给change的变量, 是一个”直接量”, 并且属于IS_VAR, 所以PHP会把传递方式改变为传值.

    所以在change中第一次debug_zval_dump的时候, 引用计数就是3了. ~

    其实, 分析到这里的时候, 我们可以类比函数的返回值做为参数的情形(第一个if判断条件). 想象赋值也和函数调用一样, 有返回值, 就容易理解多了.

    建议继续学习:

    1. Linus:为何对象引用计数必须是原子的    (阅读:11347)
    2. 不定参数的应用 function(fmt, …)    (阅读:3921)
    3. 字符引用和空白字符    (阅读:3584)
    4. 函数式编程    (阅读:3591)
    5. JavaScript的5种调用函数的方法    (阅读:3471)
    6. 通过引用计数解决野指针的问题(C&C++)    (阅读:3360)
    7. C 语言中统一的函数指针    (阅读:2971)
    8. 变量引用可提供执行速度    (阅读:2877)
    9. C语言函数实现的另类方法    (阅读:2832)
    10. 关于在函数调用时传递string引用的必要性    (阅读:2769)
    QQ技术交流群:445447336,欢迎加入!
    扫一扫订阅我的微信号:IT技术博客大学习
    © 2009 - 2024 by blogread.cn 微博:@IT技术博客大学习

    京ICP备15002552号-1