C编程-编译时检测条件满足

Posted by 周思进 on October 25, 2019

先看下面这段代码

#define STR_LEN 32
#define NAME_LEN 8
#define PASS_LEN 8

struct user {
	char sz_name[NAME_LEN];
	char sz_pass[PASS_LEN];
};

int test(struct user *p_user)
{
	char str[STR_LEN] = {0};

	snprintf(str, sizeof(str), "%s-%s\n", p_user->sz_name, p_user->sz_pass);
	printf("str:%s\n", str);

	return 0;
}

int main(void)
{
	struct user user1 = {"abcdabc", "abcdabc"};
	
	test(&user1);

	return 0;
}

上面这段代码运行并不会存在问题,但如果后面因为一些原因修改了上述宏定义的长度,比如NAME_LEN扩大到64字节了,但并没有考虑到要修改STR_LEN长度(这里代码简单可能知道需要同步修改下STR_LEN,但实际工程代码可能就比较复杂,没有查看到一些影响的地方,重点是测试如果没有测试一些边界值,那可能都发现不了问题)。

经过修改之后,显然上述组装字符串操作就得不到想要的结果了。可能容易想到的是在snprintf代码前加个if判断,两个成员变量组装的字符串是否会超过目标存储长度,是的话就出错打印。但这就只能运行时出现这样的错误才会发现存在这样的问题。

实际上有更好的办法直接在编译阶段就拦截掉,这里可以使用到CCAN(c语言的模块仓库)中的build_assert模块代码。

其主要关键代码如下:

#define BUILD_ASSERT(cond) \
	do { (void) sizeof(char [1 - 2*!(cond)]); } while(0)

该宏的实现机制就是如果cond条件为真,那么char数组下标就是1,没有问题,但如果条件是假,那么char数组下标就是-1了,这样编译就会直接报错“array size is negative”

这样只需要在snprintf代码前面增加如下代码即可

BUILD_ASSERT((STR_LEN > sizeof(p_user->sz_name)+sizeof(p_user->sz_pass)));

只要对应宏发生变动不满足上述条件,编译时即可报错,就可以直接解决处理。

不过该机制有一个限制,那就是条件只能是在编译阶段就能确认的常量表达式,如果误写成如下代码,那编译阶段是无法发现问题的

BUILD_ASSERT((STR_LEN > strlen(p_user->sz_name)+strlen(p_user->sz_pass)));

这个就需要写的人谨记下了~


CCAN代码下载:
http://ccodearchive.net/index.html
这个开源代码也值得看看学习学习。