为什么在32位机器中 - ( - 2147483648)= - 2147483648?

Les*_*ble 62 c 32-bit twos-complement

我认为问题是自我解释的,我想它可能与溢出有关,但我仍然不太明白.引擎盖下发生了什么?

为什么-(-2147483648) = -2147483648(至少在用C编译时)?

Grz*_*ski 74

否定(未固定的)整数常量:

表达式-(-2147483648)在C中是完美定义的,但是为什么这种方式可能并不明显.

当你写时-2147483648,它形成为应用于整数常量的一元减运算符.如果2147483648不能表示为int,那么它表示为longlong long*(以先到者为准),后者的类型由C标准保证覆盖该值.

要确认这一点,您可以通过以下方式检查:

printf("%zu\n", sizeof(-2147483648));
Run Code Online (Sandbox Code Playgroud)

8在我的机器上产生.

下一步是应用第二个-运算符,在这种情况下,最终值是2147483648L(假设它最终表示为long).如果您尝试将其分配给int对象,如下所示:

int n = -(-2147483648);
Run Code Online (Sandbox Code Playgroud)

然后实际行为是实现定义的.参考标准:

C11§6.3.1.3/ 3有符号和无符号整数

否则,新类型将被签名,并且值无法在其中表示; 结果是实现定义的,或者引发实现定义的信号.

最常见的方法是简单地切断较高位.例如,GCC将其记录为:

为了转换为宽度N的类型,该值以2 ^ N的模数减少到该类型的范围内; 没有信号被提出.

从概念上讲,转换为宽度类型32可以通过按位AND运算来说明:

value & (2^32 - 1) // preserve 32 least significant bits
Run Code Online (Sandbox Code Playgroud)

根据二进制补码算法,其值n由全0和MSB(符号)位组成,表示值-2^31,即-2147483648.

否定一个int对象:

如果你试图否定int对象,它具有值-2147483648,然后假设两个补码机器,程序将表现出未定义的行为:

n = -n; // UB if n == INT_MIN and INT_MAX == 2147483647
Run Code Online (Sandbox Code Playgroud)

C11§6.5/ 5表达式

如果在计算表达式期间发生异常情况(即,如果结果未在数学上定义或未在其类型的可表示值范围内),则行为未定义.

其他参考:


*)在撤销的C90标准中,没有long long类型,规则也不同.具体而言,序列无后缀十进制是int,long int,unsigned long int(C90§6.1.3.2整数常数).

†)这是因为LLONG_MAX,必须至少+9223372036854775807(C11§5.2.4.2.1/ 1).

  • 它仅在现代编译器(C99或C++ 11及更高版本)中被提升为"long long".在旧的编译器,它会给出惊喜的效果[为什么它是-2147483648(INT)-2147483648之间的不同(http://stackoverflow.com/q/12620753/995714)[铸造最小的32位整数(-2147483648 )浮动给出正数(2147483648.0)](http://stackoverflow.com/q/11536389/995714) (10认同)
  • 这个答案应该强调它只适用于整数文字; 特别是,它不适用于否定包含值`-2147483648`的`int`对象. (2认同)
  • `2147483648`不会被提升为任何东西.它的类型为`int`,`long`或`long long`(取其适合的最小值)."promote"是指实际上具有比"int"更窄的类型的值,在表达式中使用时会更改为不同类型的值 (2认同)

Ant*_*ala 16

注意:此答案不适用于许多编译器仍在使用的过时的ISO C90标准

首先,在C99,C11上,表达式-(-2147483648) == -2147483648实际上是错误的:

int is_it_true = (-(-2147483648) == -2147483648);
printf("%d\n", is_it_true);
Run Code Online (Sandbox Code Playgroud)

版画

0
Run Code Online (Sandbox Code Playgroud)

那么这个评估结果如何可能呢?该机器使用32位二进制补码整数.这2147483648是一个整数常量,完全不适合32位,因此它将是long int或者long long int取决于它所适合的第一个.否定将导致-2147483648- 并且再次,即使数字-2147483648可以适合32位整数,表达式也-2147483648包含一个> 32位正整数,前面带有一元-!

您可以尝试以下程序:

#include <stdio.h>

int main() {
    printf("%zu\n", sizeof(2147483647));
    printf("%zu\n", sizeof(2147483648));
    printf("%zu\n", sizeof(-2147483648));
}
Run Code Online (Sandbox Code Playgroud)

这种机器上的输出最可能是4,8和8.

现在,-2147483648否定将再次导致+214783648,仍然是类型long intlong long int,并且一切都很好.

在C99,C11中,整数常量表达式-(-2147483648)在所有符合的实现上都是明确定义的.


现在,当此值分配给类型的变量时int,具有32位和2的补码表示,该值无法在其中表示 - 32位2的补码上的值范围为-2147483648至2147483647.

C11标准6.3.1.3p3表示以下整数转换:

  • [何时]签署新类型并且无法在其中表示值; 结果是实现定义的,或者引发实现定义的信号.

也就是说,C标准实际上并没有定义这种情况下的值是什么,或者不排除由于信号被引发而导致程序执行停止的可能性,而是将其留给实现(即编译器) )决定如何处理它(C11 3.4.1):

实现定义的行为

未指定的行为,其中每个实现都记录了如何进行选择

(3.19.1):

实现定义的值

未指定的值,其中每个实现记录了如何进行选择


在您的情况下,实现定义的行为是该值是32个最低位[*].由于2的补码,(长)长int值0x80000000的位设置为31,所有其他位清零.在32位二进制补码整数中,位31是符号位 - 意味着该数字为负; 所有值位归零表示该值是最小可表示数,即INT_MIN.


[*] GCC 在这种情况下记录了其实现定义的行为,如下所示:

当该值无法在该类型的对象中表示时,将整数转换为有符号整数类型的结果或信号(C90 6.2.1.2,C99和C11 6.3.1.3).

对于转换为宽度类型N,该值以模数减小2^N到该类型的范围内; 没有信号被提出.


Joh*_*ger 5

这不是一个C问题,因为在具有32位二进制补码表示的C实现上int,将一元否定运算符应用于int具有该值的值-2147483648未定义的.也就是说,C语言特别拒绝指定评估这种操作的结果.

然而,更一般地考虑如何在-二进制补码算法中定义一元运算符:通过翻转其二进制表示的所有位并添加来形成正数x的逆1.该相同定义也适用于除了其符号位集之外至少有一位的任何负数.

但是,对于没有设置值位的两个数字,出现了一些小问题:0,根本没有设置位,以及只设置其符号位的数字(以32位表示形式的-2147483648).当您翻转其中任何一个的所有位时,最终会设置所有值位.因此,当您随后添加1时,结果会溢出值位.如果您想象执行添加就像数字是无符号的那样,将符号位视为值位,那么您就得到了

    -2147483648 (decimal representation)
-->  0x80000000 (convert to hex)
-->  0x7fffffff (flip bits)
-->  0x80000000 (add one)
--> -2147483648 (convert to decimal)
Run Code Online (Sandbox Code Playgroud)

类似地适用于反转零,但在这种情况下,添加1时的溢出也会溢出以前的符号位.如果忽略溢出,则得到的32个低位全部为零,因此-0 == 0.

  • 我害怕Grzegorz Szpetkowski钉它:表达式` - ( - 2147483648)`是完美定义的. (4认同)
  • 它是完美定义的,因为[`-2147483648`在现代编译器中是'long long`而在旧版本中是`unsigned long`](http://stackoverflow.com/a/8511877/995714).结果在两种情况下都不同,但它们仍然是定义的 (3认同)