何时在复杂表达式中评估后缀增量运算符?

时间:2009-08-03 13:39:51

标签: c++ c operators

说我有这样的表达

short v = ( ( p[ i++ ] & 0xFF ) << 4 | ( p[ i ] & 0xF0000000 ) >> 28;

p是指向动态分配的32位整数数组的指针。

完全i会递增吗?我注意到上面的代码为v提供了与以下代码不同的值:

short v = ( p[ i++ ] & 0xFF) <<   4;
v |= ( p[ i ] & 0xF0000000 ) >>  28;

我对此行为的最佳猜测是i在评估上述|的右侧之前不会递增。

任何见解都将受到赞赏!

提前致谢,

\比约恩

5 个答案:

答案 0 :(得分:13)

i在下一个序列点之前的某个时间递增。您给出的表达式中唯一的序列点位于语句的末尾 - 因此“在语句结束之前的某个时间”是这种情况下的答案。

这就是为什么你不应该修改左值并且在没有插入序列点的情况下读取它的值 - 结果是不确定的。

&amp;&amp;,||,逗号和?运算符引入序列点,以及表达式和函数调用的结束(后者意味着如果你执行f(i ++,&amp; i),f()的主体如果使用指针则会看到更新的值检查i)。

答案 1 :(得分: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)

现在,只要不违反上述'after'子句,编译器就可以自由地重新排列子表达式。因此,一个快速简单的优化是向上移动3个槽然后执行共同表达式移除(1)和(3)(现在彼此相邻)是相同的,因此我们可以消除(3)

但是编译器不需要进行优化(并且可能比我好,并且还有其他技巧)。但是你可以看到(a1)的值总是如你所期望的那样,但是(a2)的值将取决于编译器决定执行其他子表达式的顺序。

唯一可以保证编译器不能将子表达式移动到序列点之外。你最常见的序列点是';' (声明的结尾)。还有其他人,但我会避免使用这些知识,因为大多数人都不了解编译器的工作原理。如果您编写使用序列点技巧的代码,那么有人可能会重新考虑代码以使其看起来更具可读性,现在您的技巧刚刚转变为未定义的行为。

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)

这里的一切都很明确,因为对i的写入被起诉而不是在同一个表达式中重新读取。

简单的规则。不要在较大的表达式中使用++或 - 运算符。 您的代码看起来就像这样:

++i; // prefer pre-increment (it makes no difference here, but is a useful habit)
v = ( ( p[ i ] & 0xFF ) << 4 | ( p[ i ] & 0xF0000000 ) >> 28;

有关评估顺序的详细说明,请参阅本文:
What are all the common undefined behaviours that a C++ programmer should know about?

答案 2 :(得分:9)

第一个例子是未定义的行为。您不能在同样更改变量值的表达式中多次读取变量。请参阅this(以及互联网上的其他地方)。

答案 3 :(得分:3)

有时在表达结束之前。

读取一个对象也是未定义的,该对象除了确定新值之外还会被修改,因为未定义两次写入对象。你甚至可能得到不一致的价值(即读取不是旧的或新的价值的东西)。

答案 4 :(得分:1)

您的表达式有未定义的行为,例如,请参阅this关于C和C ++语句中的序列点。