首页 文章

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

提问于
浏览
85

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

unsigned int To, Tf;

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

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

涉及无符号操作数的计算永远不会溢出,因为无法通过结果无符号整数类型表示的结果将以比结果类型可以表示的最大值大1的数量为模 .

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

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

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

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

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

4 回答

  • 106

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

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

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

    请注意,虽然它确实说"A computation involving unsigned operands can never overflow",这可能会让您认为它仅适用于超过上限,但这是作为句子的实际绑定部分的动机:"a result that cannot be represented by the resulting unsigned integer type is reduced modulo the number that is one greater than the largest value that can be represented by the resulting type."此短语不限于上层溢出类型的边界,并且同样适用于太低而无法表示的值 .

  • 3

    使用无符号类型时,modular arithmetic(也称为"wrap around"行为)正在发生 . 要理解这种模块化算法,只需看看这些时钟:

    enter image description here

    9 + 4 = 1 (13 mod 12),所以到另一个方向是: 1 - 4 = 9 ( - 3 mod 12) . 使用无符号类型时应用相同的原则 . 如果 result typeunsigned ,则进行模运算 .


    现在看一下将结果存储为 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 . 当你想得到数字之间的差异并确保不会应用模运算时,你应该考虑使用_2583017中定义的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)
        // ...
    

    but

    if (five - seven < 1)   // = if ((unsigned int)-2 < 1) = if (4294967294 < 1)
        // ...
    
    if (one - six < five)   // = if ((unsigned int)-5 < 5) = if (4294967291 < 5)
        // ...
    
  • 3

    那么,第一种解释是正确的 . 但是,你在这种情况下对“签名语义”的推理是错误的 .

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

    但是,第二种解释(基于"signed semantics"的解释)也需要产生相同的结果 . 即即使您在有符号类型的域中评估 0 - 1 并获得 -1 作为中间结果,此 -1 仍然需要生成 0xFFFF ,以后它将转换为无符号类型 . 即使某些平台对有符号整数使用奇异表示(1的补码,有符号幅度),在将有符号整数值转换为无符号整数值时,仍需要使用该平台来应用模运算规则 .

    例如,这个评估

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

    仍然保证在 c 中产生 UINT_MAX ,即使该平台使用外来表示的有符号整数 .

  • 92

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

    请注意,小于 unsigned int 的无符号数字可能会得到在减法之前提升为 int 类型, a-b 的行为将取决于 int 的大小 .

相关问题