无符号整数减法是否定义了行为?

时间:2011-08-28 13:59:02

标签: c standards unsigned integer-arithmetic

我遇到的代码来自一个似乎认为在结果为负时从另一个相同类型的整数中减去无符号整数的问题。因此,即使它恰好适用于大多数架构,这样的代码也是不正确的。

unsigned int To, Tf;

To = getcounter();
while (1) {
    Tf = getcounter();
    if ((Tf-To) >= TIME_LIMIT) {
        break;
    } 
}

这是我能找到的C标准中唯一含糊不清的引用。

  

涉及无符号操作数的计算永远不会溢出,因为a   结果无法由结果无符号整数表示   type是以大于最大值的数量减少的模数   可以由结果类型表示的值。

我想人们可以接受这个引用来表示当右操作数较大时,操作被调整为在模数截断数字的上下文中有意义。

0x0000 - 0x0001 == 0x 1 0000 - 0x0001 == 0xFFFF

而不是使用依赖于实现的签名语义:

0x0000 - 0x0001 ==(无符号)(0 + -1)==(0xFFFF但也是0xFFFE或0x8001)

哪种或哪种解释是对的?是否完全定义了?

4 个答案:

答案 0 :(得分:106)

当您使用 unsigned 类型时,modular arithmetic(也称为“环绕”行为)正在发生。要理解这个模运算,只需看看这些时钟:

enter image description here

9 + 4 = 1 13 mod 12 ),所以另一个方向是: 1 - 4 = 9 (< em> -3 mod 12 )。使用无符号类型时应用相同的原则。如果结果类型unsigned,则会进行模运算。


现在看一下将结果存储为unsigned int

的以下操作
unsigned int five = 5, seven = 7;
unsigned int a = five - seven;      // a = (-2 % 2^32) = 4294967294 

int one = 1, six = 6;
unsigned int b = one - six;         // b = (-5 % 2^32) = 4294967291

如果您想确保结果为signed,请将其存储到signed变量中或投放到signed。如果想要获得数字之间的差异并确保不会应用模运算,那么您应该考虑使用stdlib.h中定义的abs()函数:

int c = five - seven;       // c = -2
int d = abs(five - seven);  // d =  2

要非常小心,特别是在写条件时,因为:

if (abs(five - seven) < seven)  // = if (2 < 7)
    // ...

if (five - seven < -1)          // = if (-2 < -1)
    // ...

if (one - six < 1)              // = if (-5 < 1)
    // ...

if ((int)(five - seven) < 1)    // = if (-2 < 1)
    // ...

<强>但

if (five - seven < 1)   // = if ((unsigned int)-2 < 1) = if (4294967294 < 1)
    // ...

if (one - six < five)   // = if ((unsigned int)-5 < 5) = if (4294967291 < 5)
    // ...

答案 1 :(得分:93)

在无符号类型中生成负数的减法结果是明确定义的:

  
      
  1. [...]涉及无符号操作数的计算永远不会溢出,   因为无法用结果无符号整数类型表示的结果是   减少模数可以是最大值的数字   由结果类型表示。   (ISO / IEC 9899:1999(E)§6.2.5/ 9)
  2.   

如您所见,(unsigned)0 - (unsigned)1等于-1模UINT_MAX + 1,换句话说,UINT_MAX。

请注意,虽然它确实说“涉及无符号操作数的计算永远不会溢出”,这可能会让您相信它仅适用于超出上限,但这表示为动机句子的实际绑定部分:“无法用结果无符号整数类型表示的结果是 减少模数可以是最大值的数字 由结果类型表示。“这个短语不仅限于类型上限的溢出,也适用于太低而无法表示的值。

答案 2 :(得分:3)

嗯,第一种解释是正确的。但是,你对#34;签名语义的推理&#34;在这种情况下是错误的。

同样,你的第一个解释是正确的。无符号算术遵循模运算的规则,这意味着对于32位无符号类型,0x0000 - 0x0001求值为0xFFFF

然而,第二种解释(基于&#34;签名语义&#34;的解释)也需要产生相同的结果。即即使您在已签名类型的域中评估0 - 1并获取-1作为中间结果,但在稍后将其转换为无符号时,仍需要-1生成0xFFFF类型。即使某些平台对有符号整数使用奇异表示(1&#39补,有符号幅度),在将有符号整数值转换为无符号整数时,该平台仍然需要应用模运算规则。

例如,此评估

signed int a = 0, b = 1;
unsigned int c = a - b;

仍然可以保证在UINT_MAX中生成c,即使该平台使用了外来表示的有符号整数。

答案 3 :(得分:3)

对于类型为unsigned int或更大的无符号数,在没有类型转换的情况下,a-b被定义为产生无符号数,当添加到b时,将产生{{} 1}}。将负数转换为无符号定义为产生数字,当加到符号反转的原始数字时,将产生零(因此将-5转换为无符号将产生一个值,当加到5时,将产生零)

请注意,小于a的无符号数字可能会在减法之前升级为unsigned intint的行为将取决于a-b的大小。