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

发布于 2024-12-01 22:45:13 字数 660 浏览 0 评论 0 原文

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

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 == (unsigned)(0 + -1) == (0xFFFF but还有 0xFFFE 或 0x8001)

哪个或哪种解释是正确的?它有定义吗?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

月光色 2024-12-08 22:45:13

当您使用无符号类型时,模算术 (也称为“环绕”行为)正在发生。要理解这个模块化算术,只需看看这些时钟:

在此处输入图像描述

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


现在看看以下将结果存储为 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 code> 变量或将其转换为 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)
    // ...

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)
    // ...

When you work with unsigned types, modular arithmetic (also known as "wrap around" behavior) is taking place. To understand this modular arithmetic, just have a look at these clocks:

enter image description here

9 + 4 = 1 (13 mod 12), so to the other direction it is: 1 - 4 = 9 (-3 mod 12). The same principle is applied while working with unsigned types. If the result type is unsigned, then modular arithmetic takes place.


Now look at the following operations storing the result as an 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

When you want to make sure that the result is signed, then stored it into signed variable or cast it to signed. When you want to get the difference between numbers and make sure that the modular arithmetic will not be applied, then you should consider using abs() function defined in stdlib.h:

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

Be very careful, especially while writing conditions, because:

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)
    // ...
许仙没带伞 2024-12-08 22:45:13

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

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

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

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

The result of a subtraction generating a negative number in an unsigned type is well-defined:

  1. [...] 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.
    (ISO/IEC 9899:1999 (E) §6.2.5/9)

As you can see, (unsigned)0 - (unsigned)1 equals -1 modulo UINT_MAX+1, or in other words, UINT_MAX.

Note that although it does say "A computation involving unsigned operands can never overflow", which might lead you to believe that it applies only for exceeding the upper limit, this is presented as a motivation for the actual binding part of the sentence: "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." This phrase is not restricted to overflow of the upper bound of the type, and applies equally to values too low to be represented.

傲娇萝莉攻 2024-12-08 22:45:13

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

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

然而,还需要第二种解释(基于“签名语义”的解释)才能产生相同的结果。即即使你在有符号类型的域中计算 0 - 1 并获得 -1 作为中间结果,这个 -1 仍然是必需的稍后转换为无符号类型时生成 0xFFFF。即使某些平台使用有符号整数的奇异表示(1 的补码、有符号大小),该平台在将有符号整数值转换为无符号整数值时仍然需要应用模算术规则。

例如,

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

即使平台使用有符号整数的奇异表示,此评估仍然可以保证在 c 中生成 UINT_MAX

Well, the first interpretation is correct. However, your reasoning about the "signed semantics" in this context is wrong.

Again, your first interpretation is correct. Unsigned arithmetic follow the rules of modulo arithmetic, meaning that 0x0000 - 0x0001 evaluates to 0xFFFF for 32-bit unsigned types.

However, the second interpretation (the one based on "signed semantics") is also required to produce the same result. I.e. even if you evaluate 0 - 1 in the domain of signed type and obtain -1 as the intermediate result, this -1 is still required to produce 0xFFFF when later it gets converted to unsigned type. Even if some platform uses an exotic representation for signed integers (1's complement, signed magnitude), this platform is still required to apply rules of modulo arithmetic when converting signed integer values to unsigned ones.

For example, this evaluation

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

is still guaranteed to produce UINT_MAX in c, even if the platform is using an exotic representation for signed integers.

猥琐帝 2024-12-08 22:45:13

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

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

With unsigned numbers of type unsigned int or larger, in the absence of type conversions, a-b is defined as yielding the unsigned number which, when added to b, will yield a. Conversion of a negative number to unsigned is defined as yielding the number which, when added to the sign-reversed original number, will yield zero (so converting -5 to unsigned will yield a value which, when added to 5, will yield zero).

Note that unsigned numbers smaller than unsigned int may get promoted to type int before the subtraction, the behavior of a-b will depend upon the size of int.

熟人话多 2024-12-08 22:45:13

好吧,无符号整数减法已经定义了行为,但这也是一件棘手的事情。当您减去两个无符号整数时,如果未显式指定结果(左值)类型,则结果将提升为更高类型 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)

Well, an unsigned integer subtraction has defined behavior, also it is a tricky thing. When you subtract two unsigned integers, result is promoted to higher type int if result (lvalue) type is not specified explicitly. In the latter case, for example, int8_t result = a - b; (where a and b have int8_t type) you can obtain very weird behavior. I mean you may loss transitivity property (i.e. if a > b and b > c it is true that a > c).
The loss of transitivity can destroy a tree-type data structure work. Care must be taken not to provide comparison function for sorting, searching, tree building that uses unsigned integer subtraction to deduce which key is higher or lower.

See example below.

#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)
与酒说心事 2024-12-08 22:45:13
int d = abs(five - seven);  // d =  2

std::abs 不“适合”无符号整数。但需要演员阵容。

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

std::abs is not "suitable" for unsigned integers. A cast is needed though.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文