PAYATHON 2026

不使用 CHAR_BIT,能否在 C 翻译期确定每字节的位数?

数据小陈

结论

在题述 Clang 环境中,可以继续使用 __CHAR_BIT__。这个宏给出目标平台的字节位数,无需包含 <limits.h>;Clang 的头文件源码也用它来定义 CHAR_BIT。不过,它是编译器实现提供的宏,不能假定所有 C 编译器都支持。Clang limits.h 源码

如果还想避免使用编译器扩展宏,可以从 (unsigned char)-1 推导字节位数,并将结果写成整数常量表达式。下面的实现支持 8、16、32、64 位字节,遇到其他位数时会在翻译期报错。它不能当作适用于任意 C 实现的通用公式。

为什么可以从 unsigned char 入手

将超出范围的值转换为无符号整数类型时,值会按该类型的取值范围进行模转换。因此:

(unsigned char)-1

得到的是 unsigned char 的最大值,无须引用 UCHAR_MAX。

unsigned char 采用纯二进制表示,取值范围是 0 到 2^CHAR_BIT - 1。确定最大值对应的二进制位数,就能确定 CHAR_BIT。C11 委员会草案 N1570,6.2.6.1、6.3.1.3

只针对 Clang:封装 CHAR_BIT

可以用项目自己的宏名,将依赖编译器的部分集中在一处:

#ifndef __CHAR_BIT__
#error "Compiler does not provide __CHAR_BIT__"
#endif

#define PROJECT_CHAR_BIT __CHAR_BIT__

PROJECT_CHAR_BIT 可以用于数组长度、整数常量表达式以及 #if。交叉编译时,要确保编译命令正确选择了目标平台,因为这个宏描述的是编译目标。Clang 预定义宏实现

不使用扩展宏:识别支持的字节位数

如果项目明确只支持 8、16、32、64 位字节,可以使用以下 C11 代码:

#define PROJECT_UCHAR_MAX ((unsigned char)-1)

enum {
    project_char_bit =
        PROJECT_UCHAR_MAX == 0xffULL               ? 8  :
        PROJECT_UCHAR_MAX == 0xffffULL             ? 16 :
        PROJECT_UCHAR_MAX == 0xffffffffULL         ? 32 :
        PROJECT_UCHAR_MAX == 0xffffffffffffffffULL ? 64 :
        0
};

_Static_assert(project_char_bit != 0,
               "Unsupported unsigned char width");

这里的转换、比较和条件运算都可以出现在整数常量表达式中,因此枚举值可以在翻译期确定。unsigned long long 至少能表示上述 64 位无符号常量,不需要假设 unsigned int 是 32 位。C11 委员会草案 N1570,5.2.4.2.1、6.6

若项目需要支持其他字节位数,应补充相应分支及可表示的常量,或采用具有明确位数上限的展开计算。检查不能省略,否则不受支持的平台可能得到错误结果。

这个方案得到的是 C 枚举常量,不能直接用于预处理条件:

/* Do not use an enumeration constant this way. */
#if project_char_bit == 8
#endif

预处理器不识别枚举声明,在 #if 中会把这种未定义为宏的标识符当作 0;它也不能计算包含类型转换的 PROJECT_UCHAR_MAX。需要预处理分支时,使用 __CHAR_BIT__ 更合适。C11 委员会草案 N1570,6.10.1

避免几个常见误区

  • sizeof(char) 始终为 1,单位是 C 字节,无法单独说明一个字节有多少位。
  • sizeof(int) * 8 预先假定了每字节 8 位,不能用它证明这个假定。
  • 对最大值直接加 1 可能导致有符号溢出或无符号回绕;推导位数时不必先构造 2^CHAR_BIT。
  • 通过移位试探时,要考虑整数提升和操作数宽度。移位次数达到或超过提升后左操作数的宽度,行为未定义。
  • 普通函数中的循环即使被优化器折叠,也不因此成为标准 C 的整数常量表达式。C11 委员会草案 N1570,6.3.1.1、6.5.3.4、6.5.7、6.6

包含 <limits.h> 时是否需要显式设置 --sysroot 和 -isystem,取决于工具链的安装和头文件搜索配置,并非 Clang 的普遍要求。Clang 自带的 limits.h 在特定条件下还会包含后续搜索路径中的同名头文件。要修复头文件问题,需要结合实际编译命令和错误信息分析。Clang limits.h 源码

备注:内容仅供参考。