INT_MIN * -1 是 x86 上的空操作吗?
Is INT_MIN * -1 a no-op on x86?
有符号整数在 x86 上通过二进制补码表示,其中符号位的值为 -(2^N)
。这导致典型的可表示值范围在 -2^N
和 2^N - 1
之间(例如 -32768
到 32767
)。
我很好奇,如果我将系统中的最小有符号整数值乘以 -1
以尝试 "force" 大于最大可表示值的最大值,会发生什么情况我系统上的有符号整数。
#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 中是空操作吗?
对于 C(基于问题的原始标记,以及用 C 编写的示例代码),它是未定义的行为,所以不,C 表达式 INT_MIN * -1
的结果不是 "a no-op" ,这是未定义的行为。在实践中,根据您观察的方式,结果很可能会不一致地被观察到,甚至可能在时间上不一致。别这样。
如果您想询问 x86 imul
指令,那是一个不同的问题,我相信 0xffffffff
的 0x80000000
的(32 位)imul
会产生0x80000000
。但这与您期望在 C 中看到的内容无关。
使用 gcc 4.8.5,使用以下指令计算行 y = x * -1;
:
neg %eax
取反运算翻转字的位,然后加 1。对于 32 位 2 的补码,结果是:
0x80000000 # Start value
0x7FFFFFFF # Flip bits
0x80000000 # Add 1
如您所见,计算机正在按照您的指示进行操作。这不是空操作,因为 neg
修改了 AF
、CF
、OF
、PF
、SF
和 ZF
标志。它只是所使用的二进制表示的产物。正如其他人所说,这只是未定义的行为。
在 x86 处理器上,将 "int" 乘以 -1 的指令会,
当给定位模式 0x80000000 时,结果会产生相同的位模式。
然而,重要的是要注意,x86 的 C 编译器可能会被划分
分为多个类别:
在某些编译器上,所有有符号整数运算的行为就像使用精确长度的本机运算执行数学运算一样,这相当于以无限精度执行运算并添加或减去 to/from 任何超出范围的值,无论整数模数的倍数是将其置于范围内所必需的。这种行为类似于无符号类型发生的情况,只是范围不同。在这样的编译器上,-INT_MIN == INT_MIN
.
在某些编译器上,所有有符号整数运算的行为就像以无限精度执行的一样,但可以添加或减去 to/from 任何超出范围的值任何整数的任意倍数模数,可能会产生一个结果,其行为类似于超出其类型范围的数字。这允许将 "x+1>y" 之类的表达式替换为 "x>=y",并且还可以允许将代码移出循环,而如果溢出必须始终如一地换行,则这种方式是不可能的。在这样的编译器上,-INT_MIN
可能等于 INT_MIN
,或可能等于 -(long)INT_MIN
,或低 32 位匹配 INT_MIN 的任何其他值。一些这样的编译器在将它们存储到变量时会截断超出范围的值,但有些可能不会。这种编译器的一个关键特性是,如果 "extra" 位结果在发生溢出的情况下无关紧要 [例如uint1=ushort1*ushort2;
].
一些编译器会使用整数溢出作为否定时间和因果律的基础。使用此类编译器时,必须包含逻辑以不惜一切代价防止溢出,无论由此生成的机器代码是否有助于程序满足要求。
就我个人而言,我认为第二种形式的语义最有意义;它
允许几乎所有可能的有用优化
第三,使程序员可以启用优化
这在第三种情况下是不可能的。如果第二种形式下的行为保证足以确保程序即使在发生溢出时也能满足其行为要求,那么强制程序员无论如何都要处理溢出会降低代码的效率。虽然经常会出现溢出检查是一个好主意的情况,即使以降低效率为代价,但我认为 "optimizing" 编译器要求程序员编写的代码效率低于需要的代码,这有些荒谬。
有符号整数在 x86 上通过二进制补码表示,其中符号位的值为 -(2^N)
。这导致典型的可表示值范围在 -2^N
和 2^N - 1
之间(例如 -32768
到 32767
)。
我很好奇,如果我将系统中的最小有符号整数值乘以 -1
以尝试 "force" 大于最大可表示值的最大值,会发生什么情况我系统上的有符号整数。
#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 中是空操作吗?
对于 C(基于问题的原始标记,以及用 C 编写的示例代码),它是未定义的行为,所以不,C 表达式 INT_MIN * -1
的结果不是 "a no-op" ,这是未定义的行为。在实践中,根据您观察的方式,结果很可能会不一致地被观察到,甚至可能在时间上不一致。别这样。
如果您想询问 x86 imul
指令,那是一个不同的问题,我相信 0xffffffff
的 0x80000000
的(32 位)imul
会产生0x80000000
。但这与您期望在 C 中看到的内容无关。
使用 gcc 4.8.5,使用以下指令计算行 y = x * -1;
:
neg %eax
取反运算翻转字的位,然后加 1。对于 32 位 2 的补码,结果是:
0x80000000 # Start value
0x7FFFFFFF # Flip bits
0x80000000 # Add 1
如您所见,计算机正在按照您的指示进行操作。这不是空操作,因为 neg
修改了 AF
、CF
、OF
、PF
、SF
和 ZF
标志。它只是所使用的二进制表示的产物。正如其他人所说,这只是未定义的行为。
在 x86 处理器上,将 "int" 乘以 -1 的指令会, 当给定位模式 0x80000000 时,结果会产生相同的位模式。 然而,重要的是要注意,x86 的 C 编译器可能会被划分 分为多个类别:
在某些编译器上,所有有符号整数运算的行为就像使用精确长度的本机运算执行数学运算一样,这相当于以无限精度执行运算并添加或减去 to/from 任何超出范围的值,无论整数模数的倍数是将其置于范围内所必需的。这种行为类似于无符号类型发生的情况,只是范围不同。在这样的编译器上,
-INT_MIN == INT_MIN
.在某些编译器上,所有有符号整数运算的行为就像以无限精度执行的一样,但可以添加或减去 to/from 任何超出范围的值任何整数的任意倍数模数,可能会产生一个结果,其行为类似于超出其类型范围的数字。这允许将 "x+1>y" 之类的表达式替换为 "x>=y",并且还可以允许将代码移出循环,而如果溢出必须始终如一地换行,则这种方式是不可能的。在这样的编译器上,
-INT_MIN
可能等于INT_MIN
,或可能等于-(long)INT_MIN
,或低 32 位匹配 INT_MIN 的任何其他值。一些这样的编译器在将它们存储到变量时会截断超出范围的值,但有些可能不会。这种编译器的一个关键特性是,如果 "extra" 位结果在发生溢出的情况下无关紧要 [例如uint1=ushort1*ushort2;
].一些编译器会使用整数溢出作为否定时间和因果律的基础。使用此类编译器时,必须包含逻辑以不惜一切代价防止溢出,无论由此生成的机器代码是否有助于程序满足要求。
就我个人而言,我认为第二种形式的语义最有意义;它 允许几乎所有可能的有用优化 第三,使程序员可以启用优化 这在第三种情况下是不可能的。如果第二种形式下的行为保证足以确保程序即使在发生溢出时也能满足其行为要求,那么强制程序员无论如何都要处理溢出会降低代码的效率。虽然经常会出现溢出检查是一个好主意的情况,即使以降低效率为代价,但我认为 "optimizing" 编译器要求程序员编写的代码效率低于需要的代码,这有些荒谬。