为什么 – ( – 2147483648)= – 2147483648在32位机器中?
我认为这个问题是自我解释的,我想这可能与溢出有关,但我还是不太明白。 发生了什么,在隐藏之下呢?
为什么-(-2147483648) = -2147483648
(至less在编译C时)?
否定(unsuffixed)整数常量:
expression式-(-2147483648)
在C中是完美定义的,但它可能不是很明显,为什么它是这样的。
当你写-2147483648
,它被形成为应用于整数常量的一元减运算符。 如果2147483648
不能表示为int
,则表示为long
或long long
* (以先到者为准),其中后者types由C标准保证覆盖该值。
为了证实这一点,你可以通过以下方式检查
printf("%zu\n", sizeof(-2147483648));
在我的机器上产生8
。
下一步是应用第二个操作符,在这种情况下,最终值为2147483648L
(假设它最终表示为long
)。 如果您尝试将其分配给int
对象,如下所示:
int n = -(-2147483648);
那么实际的行为是实现定义的 。 参照标准:
C11§6.3.1.3/ 3有符号和无符号整数
否则,新的types被签名并且值不能被表示; 结果是实现定义的或实现定义的信号被引发。
最常见的方法是简单地切断高位。 例如,GCC将其logging为:
为了转换为宽度为N的types,将该值以模2 ^ N减less到该types的范围内; 没有信号提出。
从概念上讲,宽度32types的转换可以通过按位与运算来说明:
value & (2^32 - 1) // preserve 32 least significant bits
根据二进制补码algorithm, n
的值由全零和MSB(符号)位置位组成,代表-2^31
值,即-2147483648
。
否定一个int
对象:
如果你试图否定int
对象,它的值为-2147483648
,那么假设二进制补码机器,程序将显示未定义的行为 :
n = -n; // UB if n == INT_MIN and INT_MAX == 2147483647
C11§6.5/ 5expression式
如果在评估expression式时发生了exception情况 (也就是说,如果结果没有在math上定义或者不在其types的可表示值范围内),则行为是不确定的。
其他参考:
- INT32-C。 确保对已签名整数的操作不会导致溢出
*)在撤回的C90标准中,没有long long
types,规则是不同的。 具体来说,未经混合的十进制的序列是int
, long int
, unsigned long int
(C90§6.1.3.2整数常量)。
†)这是由于LLONG_MAX
,它必须至less为+9223372036854775807
(C11§5.2.4.2.1/ 1)。
注意:这个答案不适用于许多编译器仍在使用的过时的ISO C90标准
首先,在C99,C11上,expression式-(-2147483648) == -2147483648
实际上是错误的 :
int is_it_true = (-(-2147483648) == -2147483648); printf("%d\n", is_it_true);
版画
0
那么如何评估这个可能性呢? 机器使用32位二进制补码整数。 2147483648
是一个整数常量,不适合32位,因此它将是long int
或long long int
具体取决于哪个位置适合哪个位置。 这个否定将导致-2147483648
– 再次,即使编号-2147483648
可以适合32位整数,expression式-2147483648
包含一个> 32位正整数,前面是一元-
!
你可以尝试下面的程序:
#include <stdio.h> int main() { printf("%zu\n", sizeof(2147483647)); printf("%zu\n", sizeof(2147483648)); printf("%zu\n", sizeof(-2147483648)); }
这种机器上的输出最可能是4,8和8。
现在, -2147483648
否定将再次导致+214783648
,它仍然是long int
types或long long int
types,并且一切正常。
在C99,C11中,整数常量expression式-(-2147483648)
在所有符合的实现上都定义良好。
现在,当这个值被分配给一个int
types的variables,32位和2的补码表示时,该值不能被表示 – 32位2的补码值范围从-2147483648到2147483647。
C11标准6.3.1.3p3说了以下的整数转换:
- [当]新的types被签名和价值不能代表; 结果是实现定义的或实现定义的信号被引发。
也就是说,C标准实际上并没有定义这种情况下的值是什么,也不排除由于信号被引发而停止执行程序的可能性,而是将其留给实现(即编译器)决定如何处理(C11 3.4.1) :
实现定义的行为
未明确的行为,其中每个实施文件如何做出select
和(3.19.1) :
实现定义的值
未指定的值在每个实现文件如何做出select
在你的情况下,实现定义的行为是该值是32个最低位[*]。 由于2的补码,(长)long int值0x80000000
的位31被置1,所有其他位被清除。 在32位二进制补码整数中,第31位是符号位 – 表示数字是负数; 所有的数值位被清零意味着数值是最小可表示的数字,即INT_MIN
。
GCC 在这种情况下logging其实现定义的行为,如下所示 :
当值不能在该types的对象(C90 6.2.1.2,C99和C11 6.3.1.3)中表示时,将整数转换为带符号整数types的结果或所产生的信号。
为了转换为宽度为
N
的types,将该值以模2^N
减less到该types的范围内; 没有信号提出。
这不是一个C问题,因为对于types为int
32位二进制补码表示的C实现,将一元否定运算符应用于具有值-2147483648
的int
的影响是未定义的 。 也就是说,C语言明确地拒绝指定评估这种操作的结果。
然而,更一般地考虑如何在二进制补码algorithm中定义一元运算符:正数x的倒数是通过翻转其二进制表示的所有比特并且加1
。 对于任何至less有一位不是其符号位的负数,同样的定义也适用。
然而,对于没有设置值位的两个数字,出现了一些小问题:0,根本没有位设置,只有符号位被设置的数字(32位表示-2147483648)。 当您翻转其中任何一个的所有位时,最终会设置所有的值位。 因此,当您随后添加1时,结果会溢出值位。 如果你想象执行加法,就好像这个数字是无符号的,把符号位当作一个值来处理,那么你就可以得到
-2147483648 (decimal representation) --> 0x80000000 (convert to hex) --> 0x7fffffff (flip bits) --> 0x80000000 (add one) --> -2147483648 (convert to decimal)
类似的情况也适用于反转零,但是在这种情况下,加1后的溢出也会溢出过去的符号位。 如果溢出被忽略,则产生的32个低位全部为零,因此-0 == 0。
我会用4位数字,只是为了使math简单,但想法是一样的。
在一个4位数字中,可能的值在0000和1111之间。这将是0到15,但是如果要表示负数,则第一位用来表示符号(0表示正数,1表示负数)。
所以1111不是15.第一位是1,这是一个负数。 为了知道它的价值,我们使用前面的答案中已经描述过的双补充方法:“反转位并加1”:
- 反转位:0000
- 加1:0001
二进制0001是十进制的,所以1111是-1。
双补码方法是双向的,所以如果你使用任何数字,它会给你倒数符号的数字的二进制表示。
现在我们来看看1000.第一位是1,所以它是一个负数。 使用双补充方法:
- 反转位:0111
- 加1:1000(十进制8)
所以1000是-8。 如果我们做-(-8)
,二进制就意味着-(1000)
,这实际上意味着在1000中使用双补码方法。正如我们上面看到的,结果也是1000.所以,在一个4位数字中, -(-8)
等于-8。
在一个32位的数字中,二进制的-2147483648
是1000..(31 zeroes)
,但是如果使用双补码的方法,结果会是相同的值(结果是相同的数字)。
这就是为什么在32位数字-(-2147483648)
等于-2147483648
它取决于C的版本,实现的细节以及我们是否在谈论variables或文字值。
首先要理解的是C中没有负整数文字“-2147483648”是一个一元减号操作,后跟一个正整数文字。
让我们假设我们正在一个典型的32位平台上运行,int和long都是32位,long long是64位,并考虑expression式。
( – ( – 2147483648)== -2147483648)
编译器需要find一个可以容纳2147483648的types,在一个通用的C99编译器上它将使用types“long long”,但是一个C90编译器可以使用types“unsigned long”。
如果编译器使用typeslong long,则没有任何溢出,并且比较是错误的。 如果编译器使用unsigned long,那么无符号的环绕规则就会起作用,并且比较是真实的。
出于同样的原因,从000(通过001 002 003 …)向前500步的磁带座位计数器的卷绕将显示500,并且从000(通过999 998 997 …)向后卷绕500步,也将显示500 。
这是二的补充符号。 当然,由于2的补码符号约定是考虑最高位的符号位,结果溢出可表示的范围,就好像20亿+ 20亿个溢出可表示的范围。
因此,处理器的“溢出”位将被置位(看到这需要访问机器的算术标志,通常在汇编程序之外的大多数编程语言中不是这样)。 当取反2的补数时,这是唯一能设置“溢出”位的值:任何其他值的否定都在2补码表示的范围内。