技术背景
在Linux内核代码(如
/usr/include/linux/kernel.h 或
/usr/include/linux/build_bug.h)中,存在如下宏定义:
#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
#define BUILD_BUG_ON_NULL(e) ((void *)sizeof(struct { int:-!!(e); }))
这些宏的作用是在编译时检查表达式 e 的值,如果 e 不为0,则会导致编译错误。那么其中的 :-!! 具体是什么意思呢?
实现步骤
1. 计算表达式 e的值
首先,对传入宏的表达式 e 进行计算。
2. 逻辑双重取反 !!(e)
使用逻辑非运算符 ! 对 e 进行两次取反。在C语言中,逻辑非运算符会将非零值转换为0,将0转换为1。因此,!!(e) 的结果是:如果 e 为0,则 !!(e) 为0;如果 e 不为0,则 !!(e) 为1。
3. 算术取反 -!!(e)
对 !!(e) 的结果进行算术取反。如果 !!(e) 为0,则 -!!(e) 为0;如果 !!(e) 为1,则 -!!(e) 为 -1。
4. 位域声明
在宏定义中,使用 struct { int: -!!(e); } 声明一个匿名的整数位域。如果 -!!(e) 为0,则声明一个宽度为0的位域;如果 -!!(e) 为 -1,则声明一个宽度为负数的位域。
5. 编译检查
在C语言中,声明宽度为负数的位域是非法的,会导致编译错误。因此,如果 e 不为0,则会触发编译错误;如果 e 为0,则一切正常,并且可以获取该结构体的大小(通常为0)。
核心代码
#include <stdio.h>
// 定义宏 BUILD_BUG_ON_ZERO
#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
int main() {
// 当 e 为 0 时,编译正常
size_t result1 = BUILD_BUG_ON_ZERO(0);
printf("result1: %zu\n", result1);
// 当 e 不为 0 时,编译错误
// size_t result2 = BUILD_BUG_ON_ZERO(1);
return 0;
}
最佳实践
替代方案
在现代C标准(如C11)中,引入了 _Static_assert 关键字,可以用于实现编译时检查。示例代码如下:
#include <stdio.h>
#include <stddef.h>
// 使用 _Static_assert 进行编译时检查
#define CHECK_ZERO(e) _Static_assert((e) == 0, "Expression must be zero!")
int main() {
// 当 e 为 0 时,编译正常
CHECK_ZERO(0);
// 当 e 不为 0 时,编译错误
// CHECK_ZERO(1);
return 0;
}
注意事项
- BUILD_BUG_ON_ZERO 和 BUILD_BUG_ON_NULL 宏只能用于编译时可计算的表达式。
- 在使用这些宏时,要确保代码的可移植性,因为某些编译器可能对负数宽度的位域有不同的处理。
常见问题
为什么不使用 assert?
assert 是一个运行时检查机制,它在程序运行时检查条件是否为真。而 BUILD_BUG_ON_ZERO 和 BUILD_BUG_ON_NULL 是编译时检查机制,它们可以在编译阶段就发现问题,避免在运行时出现潜在的错误。
sizeof(struct { int:0; })是否符合标准?
在标准C中,struct { int:0; } 的行为是未定义的。但是,GCC编译器支持这种用法,并将其视为大小为0的结构体。因此,在使用这些宏时,要注意代码的可移植性。