优化和未定义行为 #
假如我们有一个 int 类型的变量 x,那 x * 2 / 2 的结果是几?如果 C++ 把有符号整数运算溢出的结果定义为补码的内存表示,也就是说,32 位正整数 0x40'00'00'00(230)乘以 2 的结果就是 0x80'00'00'00(−231),再除以 2 的话,我们就不能得回原先的数值,而是得到了 0xC0'00'00'00(−230)。这样的话,x * 2 / 2 就不能优化为 x!
那能不能使用异常呢?也不行。跟除零不一样,整数运算溢出不会产生硬件中断。而如果我们在每条加法、减法、乘法、除法(对,除法也可能溢出—— INT_MIN / -1 就会)上都加入指令来检查是否发生溢出、并在发生溢出时报告异常的话,性能的退步将是不可接受的 [5]。
所以,C++ 的处理方式就是,规定有符号整数运算溢出为未定义行为 [6],即程序员需要保证这种情况不会发生,否则后果自负。这在允许编译器把 x * 2 / 2 优化成 x 的同时,也意味着,下面这样的代码返回的结果可能会跟程序员预想的不同(参见 https://godbolt.org/z/Ex5ad6vM9%EF%BC%89%EF%BC%9A
bool test(int n)
{
return (n + 1) == INT_MIN;
}
你想的是,如果 n + 1 溢出了,应该会得到 INT_MIN 这个特殊的结果。但编译器可以认为溢出是永远不会发生的(因为正确的程序里不应该有未定义行为),因此可以直接返回 false。——这也是实际可以在 GCC 和 Clang 上测到的结果。