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

95 c standards unsigned integer-arithmetic

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

unsigned int To, Tf;

To = getcounter();
while (1) {
    Tf = getcounter();
    if ((Tf-To) >= TIME_LIMIT) {
        break;
    } 
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

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

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

Lih*_*ihO 114

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

在此输入图像描述

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
Run Code Online (Sandbox Code Playgroud)

当您想确保结果是signed,然后将其存储到signed变量或转换为signed.如果想要获得数字之间的差异并确保不应用模运算,那么您应该考虑使用以下abs()定义的函数stdlib.h:

int c = five - seven;       // c = -2
int d = abs(five - seven);  // d =  2
Run Code Online (Sandbox Code Playgroud)

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

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)
    // ...
Run Code Online (Sandbox Code Playgroud)

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

if (one - six < five)   // = if ((unsigned int)-5 < 5) = if (4294967291 < 5)
    // ...
Run Code Online (Sandbox Code Playgroud)

  • @LightnessRacesinOrbit:谢谢.我写它是因为我认为有人可能会发现它非常有帮助.我同意,这不是一个完整的答案. (5认同)
  • 很好的时钟,虽然_proof_会使这个正确的答案.这个问题的前提已经包括断言所有这一切都可能是真的. (4认同)
  • 行`int d = abs(五 - 七);`是不好的.首先计算`five - seven`:promotion将操作数类型保留为`unsigned int`,结果以模(`UINT_MAX + 1)`为模计算,并计算为'UINT_MAX-1`.然后这个值是`abs`的实际参数,这是坏消息.`abs(int)`导致传递参数的未定义行为,因为它不在范围内,并且`abs(long long)`可能保存该值,但是当返回值被强制转换为`int`时会发生未定义的行为初始化`d`. (2认同)
  • 出于同样的原因,“int c = Five - Seven;”行更直接地错误。 (2认同)

bdo*_*lan 102

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

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

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

请注意,虽然它确实说"涉及无符号操作数的计算永远不会溢出",这可能导致您认为它仅适用于超出上限,但这被表示为句子的实际绑定部分的动机:"a无法用结果无符号整数类型表示的结果以模数减少为模数,该数字大于可由结果类型表示的最大值.该短语不限于类型上限的溢出,并且同样适用于太低而无法表示的值.

  • 我现在感觉好多了,知道如果有任何无符号的加法运算会降到零并引起混乱,那是因为uint总是用来表示数学上的[ring](https://en.wikipedia.org/wiki / Ring_(mathmatics))到整数'0'到'UINT_MAX`之间,并且加法和乘法运算以'UINT_MAX + 1'为模,而不是因为溢出。但是,它确实引起了一个问题,即如果环是这样的基本数据类型,为什么该语言没有为其他尺寸的环提供更一般的支持。 (3认同)
  • 谢谢!我现在看到我失踪的解释.我认为他们本可以选择更清晰的措辞. (2认同)
  • @TheodoreMurdock 我认为这个问题的答案很简单。据我所知,它是戒指的事实是结果,而不是原因。真正的要求是无符号类型的所有位都必须参与值表示。环状行为自然而然地由此而来。如果你想要其他类型的这种行为,那么做你的算术,然后应用所需的模数;使用基本运算符。 (2认同)

AnT*_*AnT 5

嗯,第一种解释是正确的。但是,您在此上下文中对“签名语义”的推理是错误的。

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

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

比如这个评价

signed int a = 0, b = 1;
unsigned int c = a - b;
Run Code Online (Sandbox Code Playgroud)

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

  • 我认为您的意思是 16 位无符号类型,而不是 32 位。 (4认同)

sup*_*cat 5

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

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