2017-08-03 50 views
2

我一直在用c编程一段时间。但从来没有使用整数环绕的程序。我知道如果整数分配4个字节,那么整数范围变为-2,147,483,648至2,147,483,647。如果我们超过了限制,它只是环绕一下。在c中包含整数的部分

我正在使用下面的程序来了解如何环绕发生。

#include <stdio.h> 

int main() { 
int n = 4, s = 2; 

for (int i = 0; i < n; ++i) 
{ 
    for (int j = 0; j < n; ++j) 
    { 
     for (int k = 0; k < n; ++k) 
     { 
      s = 2 * s + 1; 

     } 
    } 
} 
printf("%d\n", s); 
return 0; 
} 

我使用gdb来找出变量s所采用的值。我发现当第30次执行最内层循环时,s的值变为负值,即-1073741825。然后下一次迭代变成2147483647,第32次迭代变成-1。 The snapshot of gdb

然后它永远保持为-1。我怀疑为什么在值变为-1后没有发生缠绕。我知道二进制中s的值将全部为1或FFFFFFFF(十六进制)。它永远不会改变(内部它正在更新,但我们只能看到最后32位,所以它是-1)。但这次环绕不会进入画面吗?它依赖于编译器吗?或者,gcc是否只允许环绕一次? 任何形式的帮助,将不胜感激。谢谢

+1

简短回答:适当的环绕保证*只*为无符号类型。使用签名类型,可能会发生奇怪的事情,因为它在技术上是不确定的。 –

+4

它不再更新,因为'2 *( - 1)+ 1 = -1'。 –

回答

2

严格来说,有符号整数的溢出是undefined behavior。然而,在实践中,大多数实现使用2的补码表示来整数,并且环绕将会如何描述。

考虑到这一点,让我们看看这里发生了什么。

随着循环的进行,最终s将具有值1610612735.到目前为止,没有不寻常的事情发生。现在我们乘以2并添加一个。此时结果溢出。我们来看看这些数字的十六进制表示。

1610612735d = 0101 1111 1111 1111 1111 1111 1111 1111 b = 0x5FFFFFFF 
0x5FFFFFFF * 2 = 0xBFFFFFFE 
0xBFFFFFFE + 1 = 0xBFFFFFFF 
0xBFFFFFFE = 1011 1111 1111 1111 1111 1111 1111 1111 b = -1073741825d 

从二进制的角度来看,乘以2就等于1。左移此操作移到一个值到符号位,给你一个负值。

随着下一个操作,再乘以2溢出。这一次的乘法转变0到符号位而以前有个1,所以签收再次发生变化:

0xBFFFFFFF * 2 = 0x7FFFFFFE 
0x7FFFFFFE + 1 = 0x7FFFFFFF 
0x7FFFFFFF = 0111 1111 1111 1111 1111 1111 1111 1111 b = 2147483647 

下一次迭代也溢出:

0x7FFFFFFF * 2 = 0xFFFFFFFE 
0x7FFFFFFE + 1 = 0xFFFFFFFF 
0xFFFFFFFF = 1111 1111 1111 1111 1111 1111 1111 1111 b = -1 

现在我们有-1。从现在起,没有溢出:

-1 * 2 = -2 
-2 + 1 = -1 

这里是十六进制同样的事情:

0xFFFFFFFF * 2 = 0xFFFFFFFE 
0xFFFFFFFE + 1 = 0xFFFFFFFF 

正如你可以看到加倍-1和加入1让你再次-1,所以这就是为什么它不断重复。这也与乘以2是左移1一致。

+0

“大多数实现使用2的补码表示整数和环绕将工作如何描述。”意味着2的补充机器**将环绕。尽管这是常见的行为,但即使是带补码机器的兼容编译器也可能会在各种情况下环绕,因为它是未定义的行为。那些讨厌的新C编译器利用该UB优化代码。 – chux