说我有这样的表达
short v = ( ( p[ i++ ] & 0xFF ) << 4 | ( p[ i ] & 0xF0000000 ) >> 28;
Run Code Online (Sandbox Code Playgroud)
用p作为一个指针的32位整数的动态分配的数组.
当究竟会i递增?我注意到上面的代码提供了v与以下代码不同的值:
short v = ( p[ i++ ] & 0xFF) << 4;
v |= ( p[ i ] & 0xF0000000 ) >> 28;
Run Code Online (Sandbox Code Playgroud)
我对这种行为的最佳猜测是,i在|评估上面的右侧之前不会增加.
任何见解将不胜感激!
提前致谢,
\比约恩
caf*_*caf 13
i在下一个序列点之前的某个时间递增.您给出的表达式中唯一的序列点位于语句的末尾 - 因此"在语句结束之前的某个时间"是这种情况下的答案.
这就是为什么你不应该修改左值并且在没有插入序列点的情况下读取它的值 - 结果是不确定的.
&&,||,逗号和?运算符引入序列点,以及表达式和函数调用的结束(后者意味着如果你执行f(i ++,&i),f()的主体将看到更新的值,如果它使用指针检查一世).
Mar*_*ork 13
问题是评估顺序:
C++标准没有定义子表达式的评估顺序.这样做是为了使编译器在优化中尽可能具有攻击性.
让我们分解一下:
a1 a2
v = ( ( p[ i++ ] & 0xFF ) << 4 | ( p[ i ] & 0xF0000000 ) >> 28;
-----
(1) a1 = p[i]
(2) i = i + 1 (i++) after (1)
(3) a2 = p[i]
(4) t3 = a1 & 0xFF after (1)
(5) t4 = a2 & 0xF0000000 after (3)
(6) t5 = t3 << 4 after (4)
(7) t6 = t4 >> 28 after (5)
(8) t7 = t5 | t6 after (6) and (7)
(9) v = t7 after (8)
Run Code Online (Sandbox Code Playgroud)
现在,只要不违反上述'after'子句,编译器就可以自由地重新排列子表达式.因此,一个快速简单的优化是向上移动3个槽然后执行共同表达式移除(1)和(3)(现在彼此相邻)是相同的,因此我们可以消除(3)
但是编译器不需要进行优化(并且可能比我更好,并且还有其他技巧).但是你可以看到(a1)的值总是如你所期望的那样,但是(a2)的值将取决于编译器决定执行其他子表达式的顺序.
唯一可以保证编译器不能将子表达式移动到序列点之外.你最常见的序列点是';' (声明的结尾).还有其他人,但我会避免使用这些知识,因为大多数人都不了解编译器的工作原理.如果您编写使用序列点技巧的代码,那么有人可能会重新考虑代码以使其看起来更具可读性,现在您的技巧刚刚转变为未定义的be-behavior.
short v = ( p[ i++ ] & 0xFF) << 4;
v |= ( p[ i ] & 0xF0000000 ) >> 28;
-----
(1) a1 = p[i]
(2) i = i + 1 (i++) after (1)
(4) t3 = a1 & 0xFF after (1)
(6) t5 = t3 << 4 after (4)
(A) v = t5 after (6)
------ Sequence Point
(3) a2 = p[i]
(5) t4 = a2 & 0xF0000000 after (3)
(7) t6 = t4 >> 28 after (5)
(8) t7 = v | t6 after (7)
(9) v = t7 after (8)
Run Code Online (Sandbox Code Playgroud)
在这里,一切都被很好地定义为对i的写入被起诉而不是在同一个表达式中重新读取.
简单的规则.不要在较大的表达式中使用++或 - 运算符.您的代码看起来像这样可读:
++i; // prefer pre-increment (it makes no difference here, but is a useful habit)
v = ( ( p[ i ] & 0xFF ) << 4 | ( p[ i ] & 0xF0000000 ) >> 28;
Run Code Online (Sandbox Code Playgroud)
有关评估顺序的详细说明,请参阅此文章:
C++程序员应该了解的所有常见未定义行为是什么?