先看下面这段代码
#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
这个开源代码也值得看看学习学习。