115

I have come across code from someone who appears to believe there is a problem subtracting an unsigned integer from another integer of the same type when the result would be negative. So that code like this would be incorrect even if it happens to work on most architectures.

unsigned int To, Tf;

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

This is the only vaguely relevant quote from the C standard I could find.

A computation involving unsigned operands can never overflow, because 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.

I suppose one could take that quote to mean that when the right operand is larger the operation is adjusted to be meaningful in the context of modulo truncated numbers.

i.e.

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

as opposed to using the implementation dependent signed semantics:

0x0000 - 0x0001 == (unsigned)(0 + -1) == (0xFFFF but also 0xFFFE or 0x8001)

Which or what interpretation is right? Is it defined at all?

4

6 回答 6

136

当您使用无符号类型时,会发生模运算(也称为“环绕”行为)。要了解这种模块化算法,只需看看这些时钟:

在此处输入图像描述

9 + 4 = 1 ( 13 mod 12 ),所以另一个方向是:1 - 4 = 9 ( -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. 当您想获得数字之间的差异并确保不会应用模运算时,您应该考虑使用abs()定义在中的函数stdlib.h

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)
    // ...
于 2013-02-22T17:56:56.963 回答
118

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

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

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

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

于 2011-08-28T14:06:32.693 回答
5

嗯,第一种解释是正确的。但是,您在这种情况下对“签名语义”的推理是错误的。

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

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

比如这个评价

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

仍然保证产生UINT_MAXin c,即使平台对有符号整数使用奇异的表示。

于 2013-02-22T18:22:17.620 回答
4

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

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

于 2014-01-09T17:22:18.723 回答
0

好吧,无符号整数减法已经定义了行为,这也是一件棘手的事情。当您减去两个无符号整数时,如果未明确指定结果(左值)类型,则结果将提升为更高的 int 类型。在后一种情况下,例如 int8_t result = a - b; (其中 a 和 b 具有 int8_t 类型)您可以获得非常奇怪的行为。我的意思是你可能会失去传递性(即如果 a > b 和 b > c,那么 a > c 是真的)。传递性的丧失会破坏树型数据结构的工作。必须注意不要为排序、搜索、树构建提供比较功能,使用无符号整数减法来推断哪个键更高或更低。

请参见下面的示例。

#include <stdint.h>
#include <stdio.h>

void main()
{
    uint8_t a = 255;
    uint8_t b = 100;
    uint8_t c = 150;

    printf("uint8_t a = %+d, b = %+d, c = %+d\n\n", a, b, c);

    printf("          b - a  = %+d\tpromotion to int type\n"
           " (int8_t)(b - a) = %+d\n\n"
           "          b + a  = %+d\tpromotion to int type\n"
           "(uint8_t)(b + a) = %+d\tmodular arithmetic\n"
           "     b + a %% %d = %+d\n\n", 
           b - a,  (int8_t)(b - a), 
           b + a, (uint8_t)(b + a),
           UINT8_MAX + 1,
           (b + a) % (UINT8_MAX + 1));

    printf("c %s b (b - c = %d), b %s a (b - a = %d), AND c %s a (c - a = %d)\n",
           (int8_t)(c - b) < 0 ? "<" : ">", (int8_t)(c - b),
           (int8_t)(b - a) < 0 ? "<" : ">", (int8_t)(b - a),
           (int8_t)(c - a) < 0 ? "<" : ">", (int8_t)(c - a));
}
$ ./a.out 
uint8_t a = +255, b = +100, c = +150

          b - a  = -155 promotion to int type
 (int8_t)(b - a) = +101

          b + a  = +355 promotion to int type
(uint8_t)(b + a) = +99  modular arithmetic
     b + a % 256 = +99

c > b (b - c = 50), b > a (b - a = 101), AND c < a (c - a = -105)
于 2021-11-16T09:59:51.637 回答
0
int d = abs(five - seven);  // d =  2

std::abs 不“适合”无符号整数。不过需要演员表。

于 2021-11-26T09:58:56.347 回答