• 正文
  • 相关推荐
申请入驻 产业图谱

【LeafC】C语言之宏魔法1:BUILD_BUG_ON

01/20 09:03
239
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

开门见山

在编写程序时,我们总是希望能够尽早地发现问题,而在编码的不同阶段,发现问题的手段也因时而异。

对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的拓展特性实现,并不通用。

相关推荐

登录即可解锁
  • 海量技术文章
  • 设计资源下载
  • 产业链客户资源
  • 写文章/发需求
立即登录