我创建了一个整数my_int_min并分配了值INT_MIN,
my_int_min = -my_int_min = INT_MIN = -INT_MIN = -2147483648
Run Code Online (Sandbox Code Playgroud)
应该是有效的。所以,我预测表达式的!(-my_int_min & INT_MIN)值应该是0,但实际运行结果是1。
我发现!(-my_int_min&my_int_min)等于0并且(-my_int_min&INT_MIN)等于-2147483648。为什么!(-my_int_min & INT_MIN)不等于0?代码如下。
#include <stdio.h>
#include <limits.h>
int main()
{
int my_int_min = INT_MIN;
printf("INT_MIN=%d\t my_int_min=%d\t -INT_MIN=%d\t -my_int_min=%d\n", INT_MIN, my_int_min, -INT_MIN, -my_int_min);
printf("!(-INT_MIN&INT_MIN)=%d\t !(-my_int_min&INT_MIN)=%d\n", !(-INT_MIN&INT_MIN), !(-my_int_min&INT_MIN));
printf("!(-INT_MIN&my_int_min)=%d\t !(-my_int_min&my_int_min)=%d\n", !(-INT_MIN&my_int_min), !(-my_int_min&my_int_min));
printf("(-my_int_min&INT_MIN)=%d\t !(-my_int_min&INT_MIN)=%d\n", (-my_int_min&INT_MIN), !(-my_int_min&INT_MIN));
return 0;
}
Run Code Online (Sandbox Code Playgroud)
输出:
INT_MIN=-2147483648 my_int_min=-2147483648 -INT_MIN=-2147483648 -my_int_min=-2147483648
!(-INT_MIN&INT_MIN)=0 !(-my_int_min&INT_MIN)=1
!(-INT_MIN&my_int_min)=0 !(-my_int_min&my_int_min)=0
(-my_int_min&INT_MIN)=-2147483648 !(-my_int_min&INT_MIN)=1
Run Code Online (Sandbox Code Playgroud)
查看 Apple Clang 11.0.0 为-O3x86_64 生成的代码,并带有my_int_minqualified as,volatile表明 Clang 正在通过测试 的符号来优化代码my_int_min。
如果我们评估的是! (x & INT_MIN),而不是! (-my_int_min & INT_MIN),则名义上的未优化代码可能是获取x, 的值并将其与 \xe2\x88\x922,147,483,648 进行比较,然后测试结果以确定是否打印 \xe2\x80\x9c0 \xe2\x80\x9d 或 \xe2\x80\x9c1\xe2\x80\x9d。
当我们对此进行优化时,无需执行 AND。如果x是正数或零,则其符号位为零,因此x & INT_MIN为零,并将!其更改为 1,因此我们打印 \xe2\x80\x9c1\xe2\x80\x9d。类似地,如果x为负数,则其符号位为 1,x & INT_MINisINT_MIN并将!其更改为 0,因此我们打印 \xe2\x80\x9c0\xe2\x80\x9d。为了实现这一点,Clang 生成一条比较指令,然后生成一条setle提供所需 0 或 1 的指令;AND 被优化掉了。
现在,如果我们考虑! (-x & INT_MIN),则对于所有定义的情况都是相反的:如果x是正数或零,我们打印 \xe2\x80\x9c0\xe2\x80\x9d。如果x是负数,我们打印 \xe2\x80\x9c1\xe2\x80\x9d。更仔细地观察这一点:在所有定义的情况下,其中x为 负数, \xe2\x88\x922,147,483,648 < x< 0,因此 0 < -x< +2,147,483,648,因此在负数的所有定义情况下 的符号位-x为零x,因此-x & INT_MIN是零,并且! (-x & INT_MIN)是一。
只有一种未定义的情况:xis INT_MIN,所以-x会溢出。对于这种情况,Clang 不关心生成的代码,因此它需要的只是上述定义的情况的代码。如果x是负数,我们打印 \xe2\x80\x9c1\xe2\x80\x9d,就好像-INT_MIN生成一个正数而不是包装并生成INT_MIN。
以下是相关说明以及我的评论:
\n xorl %esi, %esi // Initialize %esi to zero.\n cmpl $0, -4(%rbp) // Compare, to test the sign bit of `my_int_min`.\n setle %sil // Update %esi to one if `my_int_min` is negative.\nRun Code Online (Sandbox Code Playgroud)\n如果没有volatile,Clang 会将上面的代码替换为常量零。然而,在了解优化如何依赖于操作数的符号而不考虑溢出之后,我们可以假设各种编译器在各种情况下可能(或可能不会)执行类似的操作。