32位无符号乘以64位导致未定义的行为?

时间:2014-11-18 18:42:04

标签: c undefined-behavior

所以我有这个代码:

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类型的值(无法进一步提升它们),而乘法只会给出结果,如注释所示(modulo {{在这种情况下,这是0x100000000。)

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

我对此是否正确,如果是这样,你会如何以理智的方式避免它?

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

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

2 个答案:

答案 0 :(得分:27)

在至少为uint32_t且至少为unsigned int的无符号类型中实现乘法的最简单方法是使用类型为unsigned int的表达式。< / p>

v = 1U * s1 * s2;

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

@Deduplicator评论说,uint32_tunsigned int窄的某些编译器可能会警告分配中的隐式转换,并注意到通过使转换显式可能会抑制此类警告: / p>

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

在我看来,它看起来有点不那么优雅了。

答案 1 :(得分:10)

祝贺找到摩擦点。

一种可能的方式:

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

无论如何,看起来像<stdint.h>添加了一些typedef,保证不会小于int的类型; - )。