15

我在 C 规范中读到了一些无符号变量(特别是unsigned shortint)在整数溢出时执行一些所谓的回绕,尽管我在有符号变量上找不到任何东西,除了我留下了未定义的行为

我的教授告诉我,他们的价值观也被包围了(也许他只是指 gcc)。我认为这些位只是被截断了,而我留下的位给了我一些奇怪的价值!

环绕是什么以及它与截断位有何不同。

4

4 回答 4

24

有符号整数变量在 C 语言中没有环绕行为。算术计算期间有符号整数溢出会产生未定义的行为。请注意顺便说一句,您提到的 GCC 编译器以在优化中实现严格的溢出语义而闻名,这意味着它利用了这种未定义行为情况提供的自由度:GCC 编译器假定有符号整数值永远不会回绕。这意味着 GCC 实际上恰好是您不能依赖有符号整数类型的环绕行为的编译器之一。

例如,GCC 编译器可以假设对于变量int i以下条件

if (i > 0 && i + 1 > 0)

相当于一个单纯的

if (i > 0)

这正是严格溢出语义的含义。

无符号整数类型实现模运算。模数相等2^N,其中N是类型的值表示中的位数。出于这个原因,无符号整数类型确实似乎在溢出时回绕。

但是,C 语言永远不会在小于int/的域中执行算术计算unsigned int。您在问题中提到的类型unsigned short int通常会在任何计算开始之前被提升为输入int表达式(假设范围unsigned short适合范围int)。这意味着 1) 的计算unsigned short int将在 的域中进行int,溢出时会发生int溢出,2) 此类计算期间的溢出将导致未定义的行为,而不是环绕行为。

例如,此代码产生一个环绕

unsigned i = USHRT_MAX;
i *= INT_MAX; /* <- unsigned arithmetic, overflows, wraps around */

而这段代码

unsigned short i = USHRT_MAX;
i *= INT_MAX; /* <- signed arithmetic, overflows, produces undefined behavior */

导致未定义的行为。

如果没有int发生溢出并且结果被转换回一个unsigned short int类型,它再次被模数减少2^N,这看起来好像值已经回绕了。

于 2013-11-07T17:54:59.480 回答
11

想象一下,您有一个只有 3 位宽的数据类型。这允许您表示 8 个不同的值,从 0 到 7。如果将 1 加到 7,您将“环绕”回 0,因为您没有足够的位来表示值 8 (1000)。

对于无符号类型,这种行为是明确定义的。它对有符号类型没有很好的定义,因为有多种表示有符号值的方法,并且溢出的结果将根据该方法进行不同的解释。

Sign-magnitude:最高位表示符号;0 为正,1 为负。如果我的类型又是 3 位宽,那么我可以如下表示有符号值:

000  =  0
001  =  1
010  =  2
011  =  3
100  = -0
101  = -1
110  = -2
111  = -3

由于符号占用了一位,所以我只有两位来编码从 0 到 3 的值。如果我将 1 加到 3,我将溢出 -0 作为结果。是的,0 有两种表示,一种是正的,一种是负的。您不会经常遇到符号幅度表示。

一个补码:负值是正值的按位倒数。同样,使用三位类型:

000  =  0
001  =  1
010  =  2
011  =  3
100  = -3
101  = -2
110  = -1 
111  = -0

我有三位来编码我的值,但范围是 [-3, 3]。如果我将 1 添加到 3,结果会溢出 -3。这与上面的符号大小结果不同。同样,使用此方法有两种 0 编码。

二进制补码:负值是正值的按位倒数,加 1。在三位系统中:

000  =  0
001  =  1
010  =  2
011  =  3
100  = -4
101  = -3
110  = -2
111  = -1

如果我将 1 与 3 相加,结果会溢出 -4,这与前两种方法不同。请注意,我们有一个稍大的值范围 [-4, 3],并且只有一种表示 0。

二进制补码可能是表示有符号值的最常用方法,但它不是唯一的方法,因此 C 标准不能保证溢出有符号整数类型时会发生什么。因此它使行为未定义,因此编译器不必处理解释多个表示。

于 2013-11-07T17:56:44.777 回答
5

当有符号整数类型可以表示为符号和幅度、一个补码或二进制补码时,未定义的行为来自早期的可移植性问题。

如今,所有架构都将整数表示为可以环绕的二进制补码。但要小心:因为你的编译器正确地假设你不会运行未定义的行为,所以在优化时你可能会遇到奇怪的错误。

于 2013-11-07T17:29:49.570 回答
3

在有符号的 8 位整数中,环绕的直观定义可能看起来像从 +127 到 -128 —— 在二进制补码中:0111111 (127) 和 1000000 (-128)。如您所见,这是递增二进制数据的自然过程——无需考虑它表示整数、有符号或无符号。反直觉地,实际溢出发生在从无符号整数的环绕意义中从 -1 (11111111) 移动到 0 (00000000) 时。

这并没有回答更深层次的问题,即当有符号整数溢出时正确的行为是什么,因为根据标准没有“正确”的行为。

于 2013-11-07T17:51:05.560 回答