提问者:小点点

如何在固定宽度类型上强制无符号算术?


下面的(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大小,某些乘法可以对提升为(有符号)int的参数执行,结果溢出(有符号的)int,因此就标准而言,结果未定义;并且可以想象的是错误的结果,特别是在不使用二的补码的机器上(越来越少)。

如果int是32位(分别为16位、64位、80位或128位),则当x0xFFFFF(分别为16位、64位、80位或128位)时,sqr16(分别为sqr8sqr32sqr64)发生这种情况。这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 并且保持无符号;因此,左乘法,然后是右乘法,作为无符号执行,因此被明确定义以在必要数量的低阶位中产生正确的结果;与结果宽度的最终隐式强制转换相同。

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

我们还有什么其他选择,在可能更好的性能、可读性和简单性之间取得合理的平衡?


共3个答案

匿名用户

您已经发现了整数类型别名的一个基本缺点

简而言之:您不能使用别名类型来执行通常的模2N算术运算。您需要使用其类型(已知!)转换秩至少是< code>int的秩。

一般来说,解决方案是将操作数转换为相应的最小值<code>unsigned int</code>、<code>signed long int</code>或<code>无符号long int>(前提是您的平台没有扩展的整数类型),然后计算表达式,然后转换回原始类型(具有正确的模块化行为)。在C语言中,您可能可以编写一个类型特征,以可移植的方式找出正确的类型。

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

匿名用户

对于较窄的无符号类型,您无法避免不可避免的将类型提升为 int

它更像是乘法运算符的一个属性。

为了避免未定义的行为极限情况,您唯一能做的就是在使用无符号类型时不要使用乘法,因为它们的最大值的平方会溢出< code>int。

幸运的是(除非您在嵌入式领域工作,您可以随时查阅文档以了解准确的行为),您可以在很大程度上把< code>unsigned short托付给历史:< code>int和它的< code>unsigned表亲很可能不会更慢,甚至可能更快。

匿名用户

如何在固定宽度类型上强制无符号算术?
我们还有什么其他选择,...?

通过使用固定宽度类型,这些类型至少与函数参数类型的无符号宽度一样宽。

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

#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_tfo16(uint16 x)可能没有采取预防措施来接收超出uint16_t范围的值。

x16 = foo16(x32);

如果这一切都更好?我仍然喜欢显式<code>1u</code>,如

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