为什么对值为 -2147483648 的表达式进行逻辑非运算会产生 1?

WXl*_*Xll -1 c c++ gcc

我创建了一个整数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)

Eri*_*hil 5

查看 Apple Clang 11.0.0 为-O3x86_64 生成的代码,并带有my_int_minqualified as,volatile表明 Clang 正在通过测试 的符号来优化代码my_int_min

\n

如果我们评估的是! (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。

\n

当我们对此进行优化时,无需执行 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 被优化掉了。

\n

现在,如果我们考虑! (-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)是一。

\n

只有一种未定义的情况:xis INT_MIN,所以-x会溢出。对于这种情况,Clang 不关心生成的代码,因此它需要的只是上述定义的情况的代码。如果x是负数,我们打印 \xe2\x80\x9c1\xe2\x80\x9d,就好像-INT_MIN生成一个正数而不是包装并生成INT_MIN

\n

以下是相关说明以及我的评论:

\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.\n
Run Code Online (Sandbox Code Playgroud)\n

如果没有volatile,Clang 会将上面的代码替换为常量零。然而,在了解优化如何依赖于操作数的符号而不考虑溢出之后,我们可以假设各种编译器在各种情况下可能(或可能不会)执行类似的操作。

\n