【问题标题】:Is INT_MIN * -1 a no-op on x86?INT_MIN * -1 在 x86 上是无操作的吗?
【发布时间】:2016-07-14 21:09:54
【问题描述】:

有符号整数在 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("%d\n%d\n", x, y);

    return 0;
}

这导致以下输出:

# gcc -std=c89 -pedantic int_min_test.c

# ./a.out
-2147483648
-2147483648

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

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

【问题讨论】:

  • 翻转不会再次精确地产生最小的可能值吗?
  • 这是未定义的行为(对于 2 的补码) - 与 x86 无关。
  • @KerrekSB 所说的。如果你想在没有 UB 的情况下观察这一点,只需使用无符号类型。
  • 它完全包装,它只是包装到您开始使用的值。没有证据表明没有溢出。

标签: x86 undefined-behavior signed integer-overflow


【解决方案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 标志。它只是正在使用的二进制表示的产物。正如其他人所说,这只是未定义的行为。

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • 是的,我对 x86 操作码行为感兴趣。我现在删除了C 标签以帮助防止混淆。
    • 当问题中的代码是 C 时,仅更改标记并没有真正的帮助。如果您想询问 asm,请使用 asm 示例代码。 C 不直接转换为 asm。
    • 这是一个很好的观点,由于未定义的行为,C 实现可以产生任何操作码序列。我将重写一个更好的问题以正确关注 x86 行为。
    • 是的,imul 产生完整的双宽度结果并截断。所以你可以用calc0x80000000 * 0xffffffff => 0x7fffffff80000000(无符号计算,这很好,因为我们只保留下半部分)。所以是的,下半部分是0x80000000,这就是你从两个操作数或立即数形式的 imul 中得到的结果。
    【解决方案3】:

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

    1. 在某些编译器上,所有带符号整数运算的行为就像是使用精确长度的本机运算执行数学运算一样,这相当于以无限精度执行运算并对任何输出进行加减运算-of-range 值,无论整数模数的倍数是否需要将其置于范围内。这种行为类似于无符号类型所发生的情况,只是范围不同。在此类编译器上,-INT_MIN == INT_MIN.

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-01-04
      • 1970-01-01
      • 1970-01-01
      • 2011-10-03
      • 1970-01-01
      • 2010-09-20
      • 1970-01-01
      相关资源
      最近更新 更多