【问题标题】:unsigned integer addition and undefined behavior in C90C90 中的无符号整数加法和未定义行为
【发布时间】:2014-04-01 13:00:41
【问题描述】:

解决了!

相关段落可以在 C90 ISO 9899:1990 6.1.2.5 Types 中找到:

"[..] 涉及无符号操作数的计算永远不会溢出,因为 [...]"

因此 9899:1990 6.3 不能应用,因此不能是未定义的行为。

感谢 Keith Thompson 帮助我阅读。 :-)

[2014-03-14] 显然,无符号短整数可能会溢出,从而导致未定义的行为,具体取决于目标环境。如果短无符号整数在算术上提升为 int,则会发生这种情况。详情见updated answer 和 supercat 的 cmets。感谢双方。 :-)

原问题:

首先,对不起我的英语不好……我尽力了。这是我的第一个问题,我认为这是一个非常愚蠢的问题。

由于一些可悲的原因,我的公司仍然坚持使用 ANSI C90 (ANSI/ISO 9899:1990),因此我手头有该标准的旧副本,但仍然缺少更正和修正。

我有一个非常简单的问题,并且在我学习期间非常清楚答案 - 直到我尝试按照标准阅读它。

如果我在加法时出现无符号整数溢出会发生什么。请看这段代码:

uint32_t a,b,c;
b = UINT_MAX;
c = UINT_MAX;
a = b + c; /* Overflow here - undefined behavior? */

我所知道的就是,无符号整数会在需要时回绕,一切都很好,但并非总是如此。

现在我正在寻找标准中的相应部分。

当然有 ISO 9899:1990 6.2.1.2 描述了无符号整数的环绕,无论何时转换。还有一点 6.2.1.5 的“常用算术转换”,它描述了类型如何变得更宽,主要是表达式的两个操作数具有相同的类型。

现在有 6.3 个“表达”与我有关。我引用:

“[...] 如果在计算表达式期间发生异常(即,如果 结果未在数学上定义或不在可代表值的范围内 对于它的类型)行为是未定义的。 [...]”

关于加法运算符的第 6.3.6 章说:

  • 对操作数进行通常的算术转换,这并不适用,因为一切都是 uint32_t。
  • 结果是两者之和,显然不适合 uint32_t。
  • 很好

没有说,结果值被转换为结果类型 - 所以 6.2.1.2 不适用。但随后值明显溢出,6.3 步入其中。

据我所知 - 根据 ISO 9899:1990,这是未定义的行为。我错过了什么?勘误中有什么东西吗?我是否遗漏了标准中的一行或一个单词?

我现在真的很困惑。 :-)

关于, 标记

问题更新

[2014-03-03]已解决

[2014-03-03] 因此,多亏了 Acme,我现在有了一个明确而完整的答案对于 ANSI C99(请参阅答案: "Is unsigned integer subtraction defined behavior":这是按预期明确定义的行为。尽管我也希望 适用于 ANSI C90,但鉴于上面的文本段落,我仍然无法从 ISO 9899:1990 的文本中读出它。所以我认为我的问题目前仍未得到解答

编辑:只是错别字| Edit2:添加 C90 和标准标签 | Edit3:添加问题更新 | Edit4:添加已解决部分 | Edit5:添加答案链接:-) | Edit6:更新短无符号整数溢出 | Edit7:一些错别字

【问题讨论】:

  • 我相信lysator.liu.se/c 有(在最终版本旁边)成为 ANSI C/C90 的草案。也许comp.lang.c FAQ 有助于查找副本。
  • 感谢 vonbrand - 我有一份标准的副本,但阅读能力很差。
  • 最后我是对的。这是一个愚蠢的问题! :-)

标签: c standards undefined-behavior unsigned-integer c89


【解决方案1】:

从 C90 到 C99,或从 C99 到 C11,该区域没有显着变化。

C90 6.1.2.5(没有段落编号,但它是 PDF 第 23 页的第一段)说:

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

几乎相同的措辞(“结果无符号整数类型”这一短语已更改 到“结果类型”出现在 C99 和 C11 标准的 6.2.5 第 9 段中。

唯一的重大变化(据我所知)是 C99 中关于签名转换的新增内容。在 C90 中,如果将有符号或无符号值转换为有符号整数类型,并且结果无法以目标类型表示,则结果是实现定义的。在 C99 和 C11 中,结果要么是实现定义的,要么引发实现定义的信号。 (我不知道有任何编译器利用权限来引发信号。)

正如 supercat 在评论中指出的那样,两个 unsigned short 值的乘法可能溢出。假设 short 是 16 位,int 是 32 位,并且整数表示是典型的(有符号类型的 2 补码,没有填充位)。然后给出:

unsigned short US = USHRT_MAX; // 65536

表达式中的两个操作数

us * us

unsigned short 提升到int(因为int 可以保存unsigned short 类型的所有可能值)——但是接近232 的产品无法容纳在 32 位 签名 int.

(在 1980 年代后期的 C 标准化过程中,委员会不得不在“保留值”和“保留无符号”整数提升之间进行选择。他们选择了前者。使用无符号保留语义,unsigned short 操作数会被提升为unsigned int,不会出现这个问题。)

【讨论】:

  • 啊 - 缺少链接。我只需要耐心阅读。我想我只是没有在类型章节中预期计算和溢出规则。本身 - 非常感谢。
  • 涉及两个至少与int 一样大的无符号操作数的计算不能溢出。但是,如果int 不足以容纳结果,则两个小于int 的无符号操作数的乘积可能会溢出。
  • @supercat:只有当intshort 宽,但小于两倍宽时才会发生这种情况(这当然是合法的,但在现实世界中可能不存在)。
  • @KeithThompson:如果宽度正好是原来的两倍,也会发生这种情况。在具有 64 位整数的机器上,uint32_t a = 3037000500u; uint32_b = a*a; 将生成未定义行为。我不确定将两个 uint32_t 相乘以产生 uint32_t 结果的任何有效的便携式方法。转换为 uint64_t 会起作用,但这很可怕。在 int 小于 32 位的嵌入式控制器上,转换为 unsigned 会失败。
  • @supercat:你是对的(我在阅读你的评论之前就意识到了这一点)。伊克。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-11-05
  • 1970-01-01
  • 2015-01-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多