查找 table 其内容取决于整数类型的大小

Lookup table whose contents depend on size of integer type

假设一个函数应该table 到 LP64 和 ILP32 系统,即 long int 可以是 32 位或 64 位。这个函数会有一些table的常量,但是常量本身需要基于类型的宽度。一个人为的例子:

// Find largest power of 1000 less than x, aka log base 1000 rounded down to an integer
unsigned long int intlog1000l(unsigned long int x) {
    const unsigned long int powers[] = {
        0, 1000, 1000000, 1000000000,
        1000000000000, 1000000000000000, 1000000000000000000 };
    unsigned int i;
    for (i = 0; i < sizeof(powers)/sizeof(*powers); i++)
        if (powers[i] > x) break;
    return i - 1; 
}    

如果 long int 是 64 位,则此代码可以正常工作。但是如果 long int 是 32 位,它将失败,因为最后三个常量太大而不适合。

解决此问题的一种方法是更改​​函数的接口和 table 的类型以同时使用 uint32_tuint64_t。但是,如何将其与不使用这些类型(例如 __builtin_clzl()labs())的现有 API 相结合?

另一种选择是保持接口不变,但将函数内部的参数提升到支持的最大大小,uint64_t,并在其中存储 table 元素尺寸。但这在 32 位系统上效率很低。

可以安排提供一个定义长整数大小的宏,然后将 table 的第二行放在 #if 中。这很困难,因为 sizeof() 不可用于预处理器。需要像 autoconf 这样的东西来确定大小并生成配置 header。这很难适应现有的构建过程。

打算提供全套signed/unsigned、intlong intlong long int功能,这里是另一种方式:

unsigned int intlog1000(unsigned int); // assume 32 bits
unsigned long long int intlog1000ll(unsigned long long int); // assume 64 bits
static inline unsigned long int intlog1000l(unsigned long int x)
{ sizeof(x) == sizeof(unsigned int) ? intlog1000(x) : intlog1000ll(x); }

这里假设可以安全地假设 int 和 long long int 具有一定的大小,并且 long int 的大小与其中一个相等。因为在当前存在的几乎所有 32 位平台上都是这种情况。

有没有更好的方法?

考虑一系列 #if

#include <limits.h>
const unsigned long int powers[] = {
    0, 1000, 1000000, 1000000000
    #if ULONG_MAX/1000 >= 1000000000
      , 1000000000000u
    #endif
    #if ULONG_MAX/1000 >= 1000000000000
      , 1000000000000000u
    #endif
    #if ULONG_MAX/1000 >= 1000000000000000
      , 1000000000000000000u
    #endif
    };

上述方法确实有问题,因为 "macro math' is sometimes signed (based on experience, not spec), so below code makes the reasonable assumption that the max unsigned long is about 2x max signed long. The " 嵌套”方法更好,因为它确保 "macro math" 工作,因为它取决于先前的成功。这对于 C99、11 编译器并不那么重要因为数学至少是 64 位的。这与旧的编译器有很大的不同,或者如果有人想将这个方案扩展到比 64 位更宽 unsigned long.

    #if LONG_MAX/1000 >= 500000000
      , 1000000000000u
      #if LONG_MAX/1000 >= 500000000000
        , 1000000000000000u
        #if LONG_MAX/1000 >= 5000000000000000
          , 1000000000000000000u
          #if LONG_MAX/1000 >= 5000000000000000000
            #error powers[] needs extending
          #endif
        #endif
      #endif
    #endif

"macro math" 或更好的 预处理器算术 ,在 C11(可能还有 C99)中至少使用 64 位数学运算,但至少使用 32 位运算像 C89 一样早。

For the purposes of this token conversion and evaluation, all signed integer types and all unsigned integer types act as if they have the same representation as, respectively, the types intmax_t and uintmax_t defined in the header <stdint.h>.) This includes interpreting character constants, which may involve converting escape sequences into execution character set members. C11 §6.10.1 4

您可以考虑给 table 元素类型 unsigned long long intuintmax_t(它们可能是也可能不是同一件事)。任何符合 C99 或 C11 的实现都将提供至少 64 位宽的 unsigned long long int,因此 uintmax_t 也将至少是那个宽度。

当然,这引出了您的函数如何处理大于 64 位的输入的问题。