【问题标题】:Unsigned integer underflow in CC中的无符号整数下溢
【发布时间】:2018-06-17 20:51:39
【问题描述】:

我在网站上看到了多个解决无符号整数上溢/下溢的问题。 大多数关于 underflow 的问题都涉及将负数分配给无符号整数;我不清楚的是,当从另一个 unsigned int 中减去 unsigned int 时会发生什么,例如a - b 结果是否定的。标准的相关部分是:

涉及无符号操作数的计算永远不会溢出,因为无法由生成的无符号整数类型表示的结果会以比结果类型可以表示的最大值大一的数字为模减少。

在这种情况下,您如何解释“减少”?这是否意味着UINT_MAX+1添加到否定结果中,直到它成为>= 0

我看到this question 解决了要点(基本上说标准选择谈论 overflow 但关于 modulo 的要点适用于下溢太)但我仍然不清楚:

a-b的结果是-1;根据标准,操作-1%(UINT_MAX+1) 将返回-1(如here 解释);所以我们又回到了开始的地方。

这可能过于迂腐,但 是指数学模而不是 C 的计算模吗?

【问题讨论】:

  • 不加减,不进行模运算:结果被截断为目的操作数的位数。如果unsigned 类型是32 位,则甚至不存在更多位。但是,32 位乘法的结果在处理器中可能是 64 位,在这种情况下,高 32 位将被忽略。
  • 从概念上讲,计算是使用“无限精度”完成的,结果会减少到0..UINT_MAX范围内的值。
  • @EnzoNakamura 我认为这是误导。该代码不执行任何模运算。结果要么被截断,要么如果没有更多有效位,则允许回绕。整数大小适合处理器寄存器的自然大小,无需任何后处理即可实现。
  • @WeatherVane 就像“不执行模运算:结果被截断为位数”一样,即未指定实现细节。考虑一个没有无符号乘法/除法的 36 位 CPU。这样的平台可以使用 35 位的掩码(实际上是对结果取模) - 并带有 1 个填充位和LONG_MAX == ULONG_MAX。如今,使用xxx_MAX == Uxxx_MAX 的此类机器很少存在,但规范是“产生的无符号整数类型以比结果类型可以表示的最大值大一的数字为模减少”。
  • @NeilEdelman。我不确定你是什么意思。数学模通常是欧几里得模,其中 C 的模 (%) 被截断,请参阅en.wikipedia.org/wiki/Modulo_operation

标签: c integer standards underflow


【解决方案1】:

首先,低于给定整数类型最小值的结果在 C 中不称为“下溢”。术语“下溢”是为浮点类型保留的,意味着完全不同的东西。超出整数类型的范围总是溢出,无论你越过范围的哪一端。因此,在这种情况下,您没有看到语言规范谈论“下溢”执行者这一事实并没有任何意义。

其次,您对“减少”一词的含义是绝对正确的。最终值是通过从“数学”结果中添加(或减去)UINT_MAX+1 来定义的,直到它返回到unsigned int 的范围内。这也与欧几里得“模”运算相同。

【讨论】:

  • “减少”是模加法零的已知(数学?)术语,还是只是标准的语言?
【解决方案2】:

您发布的标准部分谈论的是溢出,而不是下溢。

“是不是意味着将UINT_MAX+1加到负数上,直到>=0?”

你可以认为这就是发生的事情。抽象的结果将是相同的。已经有人问过类似的问题。查看此链接:Question about C behaviour for unsigned integer underflow 了解更多详情。

另一种思考方式是,例如,-1 原则上来自 int 类型(即 4 个字节,其中所有位均为 1)。然后,当您告诉程序将所有这些位 1 解释为 unsigned int 时,其值将被解释为 UINT_MAX

【讨论】:

  • 处理器不这样做。这是一种在纸上解释发生了什么的方法。没有后处理会不断添加,直到数字在范围内。它只是被截断了。
【解决方案3】:

在幕后,加法或减法是按位计算的,并且与符号无关。生成的代码可以使用相同的指令,而不管它是否签名。解释结果的是其他运算符,例如 a > 0。按位加或减,这会告诉你答案。 b0 - b1 = b111111111 答案是相同的,与符号无关。只有其他运算符将答案视为有符号类型的 -1 和无符号类型的 0xFF。该标准描述了这种行为,但我总是发现最容易记住它是如何工作的,并推断出我正在编写的代码的后果。

signed int adds(signed int a, signed int b)
{
    return a + b;
}

unsigned int addu(unsigned a, unsigned b)
{
    return a + b;
}


int main() {
    return 0;
}

->

adds(int, int):
  lea eax, [rdi+rsi]
  ret
addu(unsigned int, unsigned int):
  lea eax, [rdi+rsi]
  ret
main:
  xor eax, eax
  ret

【讨论】:

  • 该标准的作者预计非神秘实现会以与符号无关的方式处理多种整数数学(已发布的基本原理文档确定了哪些),但他们并未强制要求进行此类处理。在 gcc 中,由于将 xy 提升为带符号的 32 位值,在某些情况下,uint32_t mul_mod_65536(uint16_t x, uint16_t y) { return (x*y) & 0xFFFF;} 之类的代码对于 xy 的某些组合会产生异常行为。
猜你喜欢
  • 1970-01-01
  • 2013-04-10
  • 2014-12-09
  • 2011-02-15
  • 1970-01-01
  • 2012-02-29
  • 2021-03-15
  • 1970-01-01
  • 2023-03-15
相关资源
最近更新 更多