使用具有 uint64_t 类型的内置溢出函数

Using builtin overflow functions with uint64_t types

我目前正在编写一个大量使用 uint64_t 类型的程序,以增强跨平台的兼容性。 碰巧,我的程序有很多溢出的可能性,所以我想使用 built in overflow functions from gcc.

不幸的是,它们仅在 ints、long ints、long long ints 等上定义。 uintX_t 类型保证总是大小为 X 位,但对于那些类型来说情况并非如此,例如long int 不保证是 64 位的。这让我觉得,内置溢出函数不能在这里使用。

现在如何解决这个问题?

我有两种方法:

  1. 使用 stdint.h 中的 UINT64_MAX 常量并自己进行溢出预测。但是,我不是 "re-inventing the wheel".

  2. 的朋友
  3. 使用例如__builtin_add_overflow_p 函数只检查溢出。但是,我不是 100% 确定它们是否可以应用于 uint64_t

最好的方法是什么?我是否在监督一些明显的事情?

无符号溢出是根据 C 标准 (§6.2.5/9) 定义的行为

A computation involving unsigned operands can never overflow, because a result that cannot be represented by the resulting unsigned integer type is reduced modulo the number that is one greater than the largest value that can be represented by the resulting type.

所以当你有这样的加法时;

uint64_t a, b, c;

c = a + b;

你可以检查一下,如果操作溢出通过测试

int overflowed = c < a || c < b;

当然,如果你需要的话,你可以把它包装成一个函数。

Using builtin overflow functions with uint64_t
... to use the built in overflow functions from gcc.

要形成你自己的 builtin_uadd64_overflow(),不用 "re-inventing the wheel",使用 _Generic 来引导功能选择。

#define builtin_uadd64_overflow(a,b,r) _Generic(*(r), \
  unsigned: __builtin_uadd_overflow, \
  unsigned long: __builtin_uaddl_overflow, \
  unsigned long long: __builtin_uaddll_overflow \
  )(a,b,r)

以下函数对于 intlong long 类型的对象应该工作得很好;在 32 位平台上,int 函数将比使用 long long 的方法更有效;在 64 位平台上,long long 函数将与 int 函数一样高效。

int safe_add1(int  *value, int  delta) {
    int val = *value;
    int sum = delta+(unsigned int)val;
    if (((sum ^ val) & (sum ^ delta)) < 0)
        return -1;
    *value = sum;
    return 0;
}
int safe_add2(long long  *value, long long  delta) {
    long long val = *value;
    long long sum = delta+(unsigned long long)val;
    if (((sum ^ val) & (sum ^ delta)) < 0)
        return -1;
    *value = sum;
    return 0;
}

请注意,要真正有效地防止溢出,在许多情况下需要编译器使用整数溢出作为实现符合扩展的邀请,该扩展将以足够严格的方式定义整数溢出语义以满足程序要求,但足够宽松以允许优化。例如,如果程序想要计算 x+y > z,同时确保不会产生 numerically-wrong 行为的溢出未被检测到,并且如果编译器知道 xz 将相等,满足要求的最佳方法是计算 y > 0,因为无论 x+y 可以计算而不会溢出。

不幸的是,除非编译器提供识别此类结构的扩展,否则将无法编写特别有效的溢出检查代码。