C语言sizeof宏分析
本文主要介绍C语言中sizeof的原理分析
原理
sizeof 是 C 语言的一个运算符,用于取得对象类型的大小,以 C 字节为单位,结果类型是 size_t。sizeof 的操作数不是变长数组类型时,结果是整数常量,操作数不求值;变长数组类型的大小则可能需要在运行时计算。它不能用于函数类型、不完整类型或位域。
sizeof 的语义由编译器实现,不是标准 C 库函数。具体类型的大小取决于 C 实现及其目标平台。一个 C 字节不一定是 8 比特,字符类型的 sizeof 结果始终为 1。
实现方式
下面保留旧笔记中的宏作为反例,它不是 sizeof 的合法、可移植实现。原示例还缺少声明 size_t 所需的标准头文件 stddef.h;即使补上头文件,也不能消除宏中的未定义行为。
test.c文件
#define SIZEOF(type) ((size_t)(&((type*)0)[1]))
// 示例用法
int main() {
size_t size = SIZEOF(int);
return 0;
}这个宏试图从类型为 type* 的空指针出发,用 &((type*)0)[1] 得到偏移后的指针,再将它转换为 size_t。但空指针不指向实际数组对象,不能据此合法地执行加 1;即使某个编译器生成了预期数值,也不能把该表达式当成类型大小的通用计算方法。
真正的 sizeof 根据操作数的类型计算大小,不需要构造空指针。原宏还把指针转换成整数,这种转换的结果由实现定义;如果结果不能由目标整数类型表示,行为也是未定义的。
代码分析
(size_t)(&((type*)0)[1]) 的运算顺序可以分解为以下步骤:
(type*)0: 将整数 0 转换为指向类型type的指针,得到一个指向类型type的空指针。((type*)0)[1]: 用空指针形成下标表达式,但没有实际数组。下标表达式本身不能与( ((type*)0) + 1)这个指针表达式等同。&((type*)0)[1]: 取地址和下标所隐含的解引用相抵消,剩余空指针加 1。这里虽然没有读取数组元素,但指针加法仍然没有合法的对象基础,行为未定义。(size_t)(&((type*)0)[1]): 将所得指针转换为size_t类型。这个转换也不保证得到以字节计的大小。
因此,不能通过“虚构数组”来替代对象边界规则。要取得类型 type 的大小,应使用 sizeof(type),而不是把空指针运算的结果转换为 size_t。
旧笔记记录了以下汇编结果。它们只能说明当时某次编译的输出,不能证明上述宏符合 C 标准;原记录未给出完整的编译器版本、目标平台和编译选项:
SIZEOF(int) 对应汇编文件中的 movq $4, -8(%rbp),将值 4 存储在相对于 %rbp 偏移为 -8 的位置。
SIZEOF(double) 对应汇编文件中的movq $8, -8(%rbp),将值 8 存储在相对于 %rbp 偏移为 -8 的位置。
这些结果不能说明空指针具有可供访问的“地址为 0 的对象”。把 0 转换为指向类型 type 的指针,得到的空指针仍不指向实际的 type 对象,不能把 type[0] 解释为合法的数组元素;((type*)0)[1] 所依赖的空指针算术仍然未定义。实际代码应直接使用 sizeof。
依据:C11 公开草案 N1570 §6.3.2.3p6、§6.5.3.2p3、§6.5.3.4、§6.5.6p8。