未定义的行为 - INT_MIN * -1 在 x86 上是否为无操作



有符号整数在x86上通过二进制补码表示,其中符号位的值为-(2^N)。这导致 -2^N2^N - 1 之间的典型可表示值范围(例如 -3276832767 )。

我很好奇,如果我在我的系统上获取最小有符号整数值并将其乘以-1,试图"强制"一个最大值大于我系统上有符号整数的最大可表示值,会发生什么。

#include <stdio.h>
#include <limits.h>
int main(void){
    signed int x, y;
    x = INT_MIN;
    y = x * -1;
    printf("%dn%dn", x, y);
    return 0;
}

这导致了以下输出:

# gcc -std=c89 -pedantic int_min_test.c
# ./a.out
-2147483648
-2147483648

我期待整数溢出(导致典型的值滚动),但似乎没有发生关于x乘以-1的操作。

在 x86 中,INT_MIN-1 的乘法是无操作吗?

使用 gcc 4.8.5,使用以下指令计算行y = x * -1;

neg    %eax

neg 操作翻转单词的位,然后加 1。对于 32 位 2 的补码,结果为:

0x80000000 # Start value
0x7FFFFFFF # Flip bits
0x80000000 # Add 1

如您所见,计算机正在做您告诉它做的事情。这不是一个无操作,因为neg修改了AFCFOFPFSFZF标志。它只是正在使用的二进制表示的工件。正如其他人所说,这只是未定义的行为。

对于 C(基于问题的原始标记和用 C 编写的示例代码),它是未定义的行为,所以不,C 表达式INT_MIN * -1的结果不是"无操作",而是未定义的行为。在实践中,根据您观察结果的方式,可能会观察到不一致的结果,甚至可能在时间上不一致。别这样。

如果您想询问有关x86 imul指令的信息,这是一个不同的问题,我相信(32位)imul 0x800000000xffffffff会产生0x80000000。但这与你应该期望在 C 中看到的内容无关。

在 x86 处理器上,将"int"乘以 -1 的指令将,当给定位模式时,0x80000000产生相同的位模式作为结果。但是,重要的是要注意,x86 的 C 编译器可能会被划分分为以下几类:

  1. 在某些编译器上,所有有符号整数运算的行为就像使用精确长度的本机运算执行数学运算一样,这相当于以无限精度执行运算,并在任何超出范围的值中添加或减去整数模数的任何倍数以使其进入范围。 这种行为类似于无符号类型发生的情况,只是范围不同。 在这样的编译器上,-INT_MIN == INT_MIN.

  2. 在某些编译器上,所有有符号整数运算的行为就像以无限精度执行一样,但可能会在任何超出范围的值中添加或减去整数模数的任何任意倍数,从而可能产生其行为类似于其类型范围之外的数字的结果。 这允许将像"x+1>y"这样的表达式替换为"x>=y",并且还可能允许代码以溢出必须一致包装时无法实现的方式移动到循环之外。 在这样的编译器上,-INT_MIN可能等于INT_MIN,或者可能等于-(long)INT_MIN,或任何其他低32位与INT_MIN匹配的值。 一些这样的编译器在将超出范围的值存储到变量时会截断它们,但有些可能不会。 这种编译器的一个关键特征是,如果在发生溢出的情况下,结果的"额外"位无关紧要,则代码不需要防止溢出[例如 uint1=ushort1*ushort2; ]。

  3. 一些编译器会使用整数溢出作为否定时间和因果律的基础。 使用此类编译器时,必须包含逻辑以防止不惜一切代价溢出,无论生成的机器代码是否因此对程序的满足要求有任何贡献。

就我个人而言,我认为语义的第二种形式最有意义;它允许几乎所有有用的优化,在第三,使程序员能够实现优化在第三种情况下,这是不可能的。 如果第二种形式的行为保证足以确保程序即使在发生溢出时也能满足其行为要求,那么无论如何都强迫程序员处理溢出会使代码效率降低。 虽然经常会出现溢出检查是一个好主意,即使以降低效率为代价,但我认为"优化"编译器要求程序员编写效率低于其他需要的代码是荒谬的。

相关内容

  • 没有找到相关文章

最新更新