开门见山
在编写程序时,我们总是希望能够尽早地发现问题,而在编码的不同阶段,发现问题的手段也因时而异。
对C语言开发的项目来说,BUILD_BUG_ON
就是这么一个可以在编译期间检查代码静态约束的宏。
#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))
BUILD_BUG_ON
的原理如下:
当
condition
为false(0)时,由于1-2*!!(condition)
为 1,宏被展开为sizeof(char[1])
,这是合法语句,并且编译后不产生额外代码;当
condition
为true(非0)时,由于1-2*!!(condition)
为 -1,宏被展开为sizeof(char[-1])
,而这是非法语句,编译报错。
condition
代表的是非预期的情况,如果这种非预期的情况真的出现了,那么问题将在编译期就被发现。
语法解析
细心的朋友可能会问:sizeof(char[1])
这条语句是什么意思呢?
这个表达式中,char[1]
其实是一个类型,sizeof(char[1])
就是该类型(即长度为1的char型数组)所占内存大小。
欸?char[1]
居然是类型,那C语言是不是可以用这种方式定义数组呢:
char[2] arr = { 1, 2 };
答案是:不可以,这不符合C语言的语法。正确写法是我们熟悉的下面这种:
char arr[2] = { 1, 2 };
事实上,如果要用typedef定义一个长度为2的char型数组类型,应该这么做:
// 错误写法:
typedef char[2] char_2;
// 正确写法:
typedef char char_2[2];
char_2 arr = { 1, 2 };
这是由于C语言并不是一个完全的前缀类型声明语言,有些类型如果要做到前缀类型声明,需要用typedef这个关键字来为类型定义一个新名字。比如函数类型:
// 定义函数
void test_func(int a, char b) { … }
// 不使用typedef
void (*func)(int, char) = test_func;
// 使用typedef
typedef void (*func_t)(int, char);
func_t func = test_func;
同理,sizeof运算符也可以计算其占用内存大小:
// 以下2种写法是等价的,64位系统中结果为8
sizeof(void (*)(int, char))
sizeof(func_t)
应用场景
BUILD_BUG_ON
常用于判断编译时确定,且无法在预编译时确定的条件表达式,使用时将其放在需要判断条件约束的相关函数内即可。
使用的场景例如:结构体类型的大小是否满足某种条件,或者两个枚举常量的映射关系是否符合要求。
拓展
除了BUILD_BUG_ON
,还有一些实用的宏同样能在编译期检查代码的静态约束:
#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
#define BUILD_BUG_ON_NULL(e) ((void *)sizeof(struct { int:-!!(e); }))
原理如下:
这2个宏利用了C语言结构体位字段长度不能为负数的性质,分别实现宏参数非0报错(预期为0)和非
NULL
报错(预期为NULL
)。
在linux 3.19内核/include/linux/bug.h
中,还有个BUILD_BUG_ON
宏的升级版BUILD_BUG_ON_MSG(cond, msg)
,这个宏额外多了一个msg参数,用来设定不满足条件时编译错误所打印的信息,不过该宏是通过gcc的拓展特性实现,并不通用。