【问题标题】:How to force unsigned arithmetic on fixed-width types?如何在固定宽度类型上强制执行无符号算术?
【发布时间】:2017-04-09 17:46:49
【问题描述】:

以下(C99 和更高版本)代码想要计算一个正方形,限制为与原始固定宽度类型相同的位数。

    #include <stdint.h>
     uint8_t  sqr8( uint8_t x) { return x*x; }
    uint16_t sqr16(uint16_t x) { return x*x; }
    uint32_t sqr32(uint32_t x) { return x*x; }
    uint64_t sqr64(uint64_t x) { return x*x; }

问题是:根据 int 大小,一些乘法可以在提升为 (signed) int 的参数上执行,结果溢出 (signed) int,因此就标准而言,结果未定义;并且可能是错误的结果,尤其是在(越来越少见的)不使用 two's complement 的机器上。

如果 int 是 32 位(分别为 16 位、64 位、80 或 128 位),则 sqr16(分别为 sqr8、sqr32、sqr64)会发生这种情况当x 为0xFFFFF 时(分别为0xFF、0xFFFFFFFF、0xFFFFFFFFFFFFFFFF)。这 4 个函数都不能在 C99 下正式移植!!

C11 或更高版本或某些版本的 C++ 是否可以解决这种不幸的情况?


一个简单有效的解决方案是:

    #include <stdint.h>
     uint8_t  sqr8( uint8_t x) { return 1u*x*x; }
    uint16_t sqr16(uint16_t x) { return 1u*x*x; }
    uint32_t sqr32(uint32_t x) { return 1u*x*x; }
    uint64_t sqr64(uint64_t x) { return 1u*x*x; }

这是符合标准的,因为 1u 没有提升为 int 并且保持未签名;因此,左乘法,然后是右乘法,以无符号方式执行,因此定义良好,可以在必要数量的低位中产生正确的结果;最终隐式转换为结果宽度也是如此。

已更新: 正如comment by Marc Glisse 中所建议的那样,我使用八个编译器(三个版本的 GCC for x86 从 3.1、MS C/C++ 19.00、Keil ARM 编译器 5、两个 Cosmic ST7 变体的编译器,Microchip MCC18)。它们都生成了与原始代码完全相同的代码(我在实际项目的发布模式下使用了优化)。但是,编译器可能会生成比原始代码更差的代码。我还有其他几个嵌入式编译器可以尝试,包括一些 68K 和 PowerPC 的。

我们还有哪些其他选择,可以在可能更好的性能、可读性和简单性之间做出合理的平衡?

【问题讨论】:

  • 将乘法转换为更宽的类型,然后再转换回更窄的类型uint8_t sqr8(uint8_t x) { return (uint8_t)((uint16_t)x * (uint16_t)x); }
  • @Toby:我的理解是,即使有演员表,参数也会仍然被提升为int。
  • 是的,他们会的,但只要您始终将乘法转换为 更宽 类型,那么签名性就不会影响它。 (例如,将最大 uint16_t 值 65535 平方会导致值小于最大 uint32_t 值)。
  • 哪个编译器会在您的第二个版本中生成什么错误代码?
  • 抛开整数促销,注意如果x &gt; sqrt(UINTn_MAX),这些函数中的每一个都会溢出。这不是 C 标准的错,而是算法的错。您需要构建运行时检查或记录函数可以处理的最大值。例如,对于uint8_t,函数调用对于大于16 的值没有任何意义。我真的不明白为sqr8(17) 获取值33 会有什么用处。我宁愿得到值 289。这显然不适合 uint8_t,所以算法是问题所在。

标签: c++ c c99 standards-compliance


【解决方案1】:

您在&lt;stdint.h&gt; 中发现了整数类型别名的一个基本缺点:它们不包含有关类型转换等级的任何信息。因此,您无法控制这些类型的值是否经过整型提升,并且正如您正确观察的那样,当整型提升导致有符号类型时,表达式可能具有未定义的行为。

简而言之:您不能将别名类型用于执行模 2 的常用算术运算N。您需要使用其(已知!)转换等级至少为 int 的类型。

一般的解决方案是将您的操作数转换为unsigned int、unsigned long int 或unsigned long long int 的最小适当值(前提是您的平台没有扩展的整数类型),然后评估表达式,然后转换回到原始类型(具有正确的模块化行为)。在 C++ 中,您可能可以编写一个类型特征,以一种可移植的方式找出正确的类型。

作为一种更便宜的技巧,并且再次假设没有(更广泛的)扩展整数类型,您可以将所有内容提升为 unsigned long long int 并希望您的编译器以有效的方式进行计算。

【讨论】:

  • 我很想看看 "C++ type trait" 成语!
  • 在 C++ 中,您甚至不一定需要类型特征:它可以简单地写成 decltype(1u*x)(其中永远不会执行乘法)。
  • @KerrekSB 那是无符号类型。
  • @hvd:不过,我还是会把它写成common_type_t&lt;unsigned int, T&gt;。
  • 这并不是 stdint.h 的缺点,而是整数提升规则的缺点。它会在小整数类型和大整数类型之间造成不一致。如果整数提升规则不存在,更重要的是,如果它没有默默地改变类型的符号,C 程序就会有很多不那么微妙的错误。以更明显的错误为代价导致 b 整数溢出。
【解决方案2】:

对于更窄的无符号类型,您无法避免将类型提升为int。

它更多的是乘法运算符的属性。

为了避免未定义的行为极端情况,唯一您可以做的事情是从不在使用无符号类型时使用乘法,因为它们的最大值的平方可能会溢出@ 987654322@.

幸运的是(除非您在嵌入式世界中工作,否则您可以随时查阅文档以了解确切的行为),您可以在很大程度上将 unsigned short 委托给历史:int 和它的 unsigned 表亲很可能不会更慢,甚至可能更快。

【讨论】:

  • 对;我在问 如何 做到这一点。包括,使用更好的语言定义版本;或一些相当有效和优雅的成语。 1u* 成语相当优雅,并且完全符合标准(根据我的理解),但在许多编译器的高效测试中失败。
  • 你不能。这是不可能的。这就是我试图要说的。
【解决方案3】:

如何对固定宽度类型强制执行无符号运算?
我们还有什么其他选择,...?

对于函数参数的类型,使用至少与unsigned 一样宽的固定宽度类型。

这会导致至少转换为unsigned 作为参数传递的一部分。形式参数的类型和返回的类型仍然是经典的“固定宽度类型”。该函数的实际扩充也是固定宽度类型,但可能是更宽的固定宽度类型。

#if UINT16_MAX >= UINT_MAX
   typedef uint8  uint16_t
   typedef uint16 uint16_t
   typedef uint32 uint32_t
   typedef uint64 uint64_t
#elif UINT32_MAX >= UINT_MAX 
   typedef uint8  uint32_t
   typedef uint16 uint32_t
   typedef uint32 uint32_t
   typedef uint64 uint64_t
#elif UINT64_MAX >= UINT_MAX 
   typedef uint8  uint64_t
   typedef uint16 uint64_t
   typedef uint32 uint64_t
   typedef uint64 uint64_t
#endif

uint16_t sqr16(uint16 x) { return x*x; }
uint16_t sqr32(uint32 x) { return x*x; }
uint16_t sqr64(uint64 x) { return x*x; }

// usage
uint16_t x16 = ...;
uint32_t x32 = ...;
uint64_t x64 = ...;
x16 = sqr16(x16);
x32 = sqr32(x32);
x64 = sqr64(x64);

根据函数的不同,如果使用如下所示的更广泛的类型调用函数,这将是一个问题。 uint16_t foo16(uint16 x) 可能没有采取预防措施来防止收到超出 uint16_t 范围的值。

x16 = foo16(x32);

如果这一切更好?我仍然更喜欢明确的1u

uint16_t sqr16(uint16_t x) { return 1u*x*x; }

【讨论】:

    猜你喜欢
    • 2021-05-16
    • 2015-07-19
    • 2019-04-24
    • 2021-05-03
    • 2020-04-26
    • 1970-01-01
    • 2011-02-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多