问题是评估顺序:
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;
评估顺序的详细解释请参见这篇文章:
C++ 程序员应该了解哪些常见的未定义行为? https://stackoverflow.com/questions/367633/what-are-all-the-common-undefined-behaviour-that-c-programmer-should-know-about/367690#367690