问题1 执行结果为:1
问题2 执行结果为:bool(true)
原理解释如下:
关键词:PHP 运算符的优先级 递增/递减运算符规则
演示版本:PHP-5.6
原理分析角度:PHP源码/++操作符源码
PHP 运算符的优先级
如上所示, || 优先级 高于 = ,所以 || 返回的结果 true (boolean 类型) 复制给了变量左操作数(如题中的变量 $a)。
而赋值表达式的返回结果为左操作数的值,所以这两处可以满足 if 条件。
参考:运算符优先级
++ 运算符的计算规则
递增/递减运算符不影响布尔值。递减 NULL 值也没有效果,但是递增 NULL 的结果是 1。(参考:递增/递减运算符
从 PHP 手册 上我们可以看到 ++ 运算符对 boolean 类型变量无效,所以 问题1 中的 echo 结果为 1 , var_dump 结果为 bool(true) 。
至此,我们已经明白了这两段代码片段有这样结果的原因了。
PHP 源码角度分析 ++ 操作符实现逻辑
(以 问题1 为例)
1. 利用 vld 查看 此段代码的 opcode
➜ answer git:(master) ✗ cat -n question1.php
1 <?php
2
3 $a = 1;
4 $b = 1;
5
6 if ($a = 1 || $b = 1) {
7 ++$a;
8 }
9
10 echo $a;
11 var_dump($a);
➜ answer git:(master) ✗ php -dvld.active=1 -dvld.verbosity=3 question1.php
opcode 结果如下:
定位第七行代码,我们可以看到 ++ 操作:
opcode 为 PRE_INC
操作数类型 为 IS_CV(篮筐内容)
2. 从 PHP 源码 角度分析 ++ 操作符
2.1 词法分析
文件:Zend/zend_language_scanner.l +1343
如图示:++ 操作符所对应的 Token 为:T_INC
2.2 语法分析
文件:Zend/zend_language_parser.y +795
如图示:通过 T_INC 我们在语法解析文件中定位到了 ++ 操作符所对应的语法解析规则。
我们可以得到:
语法处理函数:zend_do_pre_incdec
opcode : ZEND_PRE_INC
分析 zend_do_pre_incdec 方法,及根据 opcode 所对应的 zend_vm 处理方法,我们可以知道,最终 opcode 在 zend_vm 中执行时,所调用的方法是 ZEND_PRE_INC_SPEC_CV_HANDLER (此处根据 opcode (ZEND_PRE_INC) 和 ++ 操作数类型 IS_CV 判断)。
查看 ZEND_PRE_INC_SPEC_CV_HANDLER 方法,源码如下:
static int ZEND_FASTCALL ZEND_PRE_INC_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
zval **var_ptr;
SAVE_OPLINE();
var_ptr = _get_zval_ptr_ptr_cv_BP_VAR_RW(execute_data, opline->op1.var TSRMLS_CC);
if (IS_CV == IS_VAR && UNEXPECTED(var_ptr == NULL)) {
zend_error_noreturn(E_ERROR, "Cannot increment/decrement overloaded objects nor string offsets");
}
if (IS_CV == IS_VAR && UNEXPECTED(*var_ptr == &EG(error_zval))) {
if (RETURN_VALUE_USED(opline)) {
PZVAL_LOCK(&EG(uninitialized_zval));
EX_T(opline->result.var).var.ptr = &EG(uninitialized_zval);
}
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
SEPARATE_ZVAL_IF_NOT_REF(var_ptr);
if (UNEXPECTED(Z_TYPE_PP(var_ptr) == IS_OBJECT)
&& Z_OBJ_HANDLER_PP(var_ptr, get)
&& Z_OBJ_HANDLER_PP(var_ptr, set)) {
/* proxy object */
zval *val = Z_OBJ_HANDLER_PP(var_ptr, get)(*var_ptr TSRMLS_CC);
Z_ADDREF_P(val);
fast_increment_function(val);
Z_OBJ_HANDLER_PP(var_ptr, set)(var_ptr, val TSRMLS_CC);
zval_ptr_dtor(&val);
} else {
fast_increment_function(*var_ptr); // 这里是重点
}
if (RETURN_VALUE_USED(opline)) {
PZVAL_LOCK(*var_ptr);
EX_T(opline->result.var).var.ptr = *var_ptr;
}
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
我们可以看到,此次调用最终会执行 fast_increment_function 方法。
继续跟进 fast_increment_function 方法
我们会发现调用了 increment_function,此方法源码如下:
ZEND_API int increment_function(zval *op1) /* {{{ */
{
switch (Z_TYPE_P(op1)) {
case IS_LONG:
if (Z_LVAL_P(op1) == LONG_MAX) {
/* switch to double */
double d = (double)Z_LVAL_P(op1);
ZVAL_DOUBLE(op1, d+1);
} else {
Z_LVAL_P(op1)++;
}
break;
case IS_DOUBLE:
Z_DVAL_P(op1) = Z_DVAL_P(op1) + 1;
break;
case IS_NULL:
ZVAL_LONG(op1, 1);
break;
case IS_STRING: {
long lval;
double dval;
switch (is_numeric_string(Z_STRVAL_P(op1), Z_STRLEN_P(op1), &lval, &dval, 0)) {
case IS_LONG:
str_efree(Z_STRVAL_P(op1));
if (lval == LONG_MAX) {
/* switch to double */
double d = (double)lval;
ZVAL_DOUBLE(op1, d+1);
} else {
ZVAL_LONG(op1, lval+1);
}
break;
case IS_DOUBLE:
str_efree(Z_STRVAL_P(op1));
ZVAL_DOUBLE(op1, dval+1);
break;
default:
/* Perl style string increment */
increment_string(op1);
break;
}
}
break;
case IS_OBJECT:
if (Z_OBJ_HANDLER_P(op1, do_operation)) {
zval *op2;
int res;
TSRMLS_FETCH();
MAKE_STD_ZVAL(op2);
ZVAL_LONG(op2, 1);
res = Z_OBJ_HANDLER_P(op1, do_operation)(ZEND_ADD, op1, op1, op2 TSRMLS_CC);
zval_ptr_dtor(&op2);
return res;
}
return FAILURE;
default:
return FAILURE;
}
return SUCCESS;
}
我们传入的操作数类型(boolean)不满足所有的 case,所以逻辑走到了 default 而最终执行了 return FAILURE;,再根据函数调用栈返回,我们发现没有对操作数做任何递增处理。
结论
此时,我们可以得出最开始的结论:
递增运算符不影响布尔值。
(当然 递减 操作符分析同理)