【问题标题】:32 bit unsigned multiply on 64 bit causing undefined behavior?64位上的32位无符号乘法导致未定义的行为?
【发布时间】:2014-11-18 18:42:03
【问题描述】:

所以我有这个代码:

uint32_t s1 = 0xFFFFFFFFU;
uint32_t s2 = 0xFFFFFFFFU;
uint32_t v;
...
v = s1 * s2; /* Only need the low 32 bits of the result */

在以下所有内容中,我假设编译器对s1s2 的范围没有任何先入之见,初始化程序仅用于上面的示例。

如果我在具有 32 位整数大小的编译器上编译它(例如在为 x86 编译时),没问题。编译器将简单地使用s1s2 作为uint32_t 类型值(无法进一步提升它们),并且乘法将简单地给出注释所述的结果(模UINT_MAX + 1,在这种情况下为0x100000000 )。

但是,如果我在具有 64 位整数大小的编译器上编译它(例如 x86-64),我可以从 C 标准推断出未定义的行为。整数提升将看到 uint32_t 可以提升为 int(64 位有符号),然后乘法将尝试将两个 int 相乘,如果它们恰好具有示例中显示的值,则会导致整数溢出,这是未定义的行为。

我对此是否正确,如果正确,您将如何以理智的方式避免它?

我发现了这个类似的问题,但涵盖了 C++:What's the best C++ way to multiply unsigned integers modularly safely?。在这里,我想得到一个适用于 C 的答案(最好与 C89 兼容)。我不会考虑让可能执行 64 位乘法的糟糕 32 位机器成为可接受的答案(通常在需要关注的代码中,32 位性能可能更关键,因为通常那些机器速度较慢)。

请注意,当使用 32 位 int 大小的编译器编译时,同样的问题可能适用于 16 位无符号整数,或者当使用具有 16 位 int 大小的编译器编译时,同样的问题可能适用于无符号字符(后者可能在编译器中很常见) 8 位 CPU:C 标准要求整数至少为 16 位,因此符合标准的编译器可能会受到影响。

【问题讨论】:

  • int 即使在大多数现代 64 位架构上也是 32 位 en.wikipedia.org/wiki/64-bit_computing#64-bit_data_models
  • 哦,来吧。 anything 是用 C 定义的吗?
  • @harold,是的:__STDC__ 是。
  • @Tim:我专门为此发布了它,尽管现在我意识到我实际上可能需要浏览设置为编译器 int 大小的整个项目独立(目前在 32 位和 64 位上都可以正常工作)来检查这些乘法和其他我认为理所当然的东西。证明你永远无法了解潜伏在 C 阴影下的所有野兽......
  • @TimSeguine 如果操作数之一是更宽的 int,则可以接受从 uint 到 int 的提升,但是将两个 uint 转换为 int 是邪恶的。

标签: c undefined-behavior


【解决方案1】:

在至少为uint32_t 且至少为unsigned int 的无符号类型中进行乘法运算的最简单方法是使用unsigned int 类型的表达式。

v = 1U * s1 * s2;

这会将1U 转换为uint32_t,或将s1s2 转换为unsigned int,具体取决于适合您的特定平台的内容。

@Deduplicator 发现某些编译器(其中uint32_tunsigned int 更窄)可能会警告赋值中的隐式转换,并指出通过显式转换可能会抑制此类警告:

v = (uint32_t) (1U * s1 * S2);

不过,在我看来,它看起来不太优雅。

【讨论】:

  • @Deduplicator 这会隐式发生,因为v 被定义为uint32_t
  • @Deduplicator 我不确定我是否同意编译器对此发出警告一定有好处,但我同意可能有编译器对此发出警告。将添加注释。
  • @hvd 这行得通,因为乘法是从左到右的,对吧?如果 1U 一直在右边,第一个乘法仍然是 s1 * s2 并转换为 int。
  • @2501 没错。 1U * s1 * s2 始终表示(1U * s1) * s2s1 * s2 * 1U 始终表示(s1 * s2) * 1U。通过不要求读者知道* 是如何绑定的,另一种可能更具可读性的替代方法是s1 * 1U * s2
  • 哦,废话,现在我也需要编写跨平台的宏来进行乘法运算。
【解决方案2】:

恭喜你找到摩擦点。

一种可能的方式:

v = (uint32_t) (UINT_MAX<=0xffffffff
  ? s1 * s2
  : (unsigned)s1 * (unsigned)s2);

无论如何,看起来像为保证不小于int 的类型添加一些类型定义到&lt;stdint.h&gt; 将是有序的;-)。

【讨论】:

  • 它甚至需要条件吗?我认为它会像 v = (unsigned)s1 * (unsigned)s2; 一样正常运行,v 的类型无论如何都会处理适当的截断,而在 32 位上,它仍然是 32 位乘法。至少如果我除了 unsigned 至少是 32 位...等等,这样的类型不是已经定义了吗?
  • 麻烦的是,int 不需要超过 16 位......不,没有这样的类型。
  • 呃,抱歉含糊其辞,我的意思是 32 位 int 编译器...嗯,我的意思是stdint.h 中的uint_least32_t,我猜甚至可以为 C89 提供合适的替换.
  • 这是否适用于 1 的补码或符号幅度?
  • @LưuVĩnhPhúc:对于无符号数,这些术语毫无意义。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-17
  • 2015-05-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-15
相关资源
最近更新 更多