在C中包含有符号和无符号变量的解释?

oru*_*pov 14 c integer-overflow

我在C规范中读到一点,无符号变量(特别是unsigned short int)执行一些所谓的整数溢出回绕,虽然我找不到任何有符号变量,除了我留下未定义的行为.我的教授告诉我他们的价值观也被包围了.(也许他只是意味着gcc)我认为这些位被截断了,我留下的位给了我一些奇怪的价值?!那么,任何人都可以解释什么是环绕的,它与仅截断位有什么不同.

AnT*_*AnT 20

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

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

if (i > 0 && i + 1 > 0)
Run Code Online (Sandbox Code Playgroud)

相当于一个人

if (i > 0)
Run Code Online (Sandbox Code Playgroud)

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

无符号整数类型实现模运算.模数相等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 */
Run Code Online (Sandbox Code Playgroud)

而这段代码

unsigned short i = USHRT_MAX;
i *= INT_MAX; /* <- signed arithmetic, overflows, produces undefined behavior */
Run Code Online (Sandbox Code Playgroud)

导致未定义的行为.

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

  • @orustammanapov:`unsigned i`代表`unsigned int i`,就像`long i`代表`long int i`.在这种情况下暗示`int`.至于你的例子,`y*= z`被解释为`y =(short)((int)y*(int)z)`,归结为'y =(short)261632`.注意,在这种情况下,算术评估期间没有溢出(完全符合`int`),但在转换回`short`期间有溢出.在这种情况下的行为是*依赖于实现*. (5认同)

Joh*_*ode 10

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

对于无符号类型,此行为已明确定义.它没有为签名类型定义良好,因为有多种方法可以表示有符号值,并且溢出的结果将根据该方法进行不同的解释.

符号幅度:最高位表示符号; 0表示正数,1表示负数.如果我的类型再次是三位宽,那么我可以表示如下的有符号值:

000  =  0
001  =  1
010  =  2
011  =  3
100  = -0
101  = -1
110  = -2
111  = -3
Run Code Online (Sandbox Code Playgroud)

由于符号占用一位,因此我只有两位来编码0到3的值.如果我加1到3,我将溢出-0作为结果.是的,有两个表示0,一个正面和一个负面.您不会经常遇到符号幅度表示.

一个补码:负值是正值的按位反转.再次,使用三位类型:

000  =  0
001  =  1
010  =  2
011  =  3
100  = -3
101  = -2
110  = -1 
111  = -0
Run Code Online (Sandbox Code Playgroud)

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

二进制补码:负值是正值的按位反转加1.在三位系统中:

000  =  0
001  =  1
010  =  2
011  =  3
100  = -4
101  = -3
110  = -2
111  = -1
Run Code Online (Sandbox Code Playgroud)

如果我加1到3,那么我将溢出-4,这与前两种方法不同.请注意,我们有一个稍大的值范围[-4,3],只有一个表示为0.

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

  • 不同的签名表示是保留签名算术溢出未定义的原始原因.现在,编译器已经开始利用未定义的行为进行优化,因此即使您知道您的目标是二进制补码架构,依靠有符号溢出产生二进制补码结果也是不安全的.参见例如http://www.airs.com/blog/archives/120 (7认同)

dia*_*pir 5

不确定的行为来自于早期的便携性问题,当符号整型可以既表现为符号和幅度,一个补码或二进制补码。

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

  • @orustammanapov:不。即使底层硬件有一些关于如何处理溢出或发生什么样的包装的内在行为,C 实现也不需要使用它。C 标准说有符号整数溢出时的行为是未定义的。这意味着 C 实现可以优化代码而不考虑溢出时发生的情况。这可能会导致行为发生,好像值被包装,或者它可能导致行为发生,好像发生了陷阱,或者它可能产生与包装可能产生的值不同的值。 (3认同)