dyx
2016-09-18 +0800
scanf
与 sscanf
堪称 C 的杀手级标准库函数,因为使用起来实在是太方便了,但是它们的安全隐患也是众所周知:
char buf[1024];
scanf("%s", buf);
这段代码里 scanf
没有办法对输入的字符串做长度限制,如果读入的字符串过长,会导致危险的缓冲区溢出。不想放弃 scanf
系列函数带来的便利,又要消除这种安全隐患,我们需要在格式描述串里加入对长度的限制:
char buf[1024];
scanf("%1023s", buf);
这虽然保证了安全却大大增加了维护的难度,因为出现了 magic number,当缓冲区大小改变时我们必须保证对应的格式描述串也得到修改,稍不注意便会滋生 bug。
在通常的C代码中,缓冲区大小通常是使用宏来定义的:
#define MAXL 1023
char buf[MAXL + 1];
所以我们自然想到在格式描述串中直接使用这个宏定义,可惜这个宏定义是一个整数,没有办法方便地融合进格式描述串,所以我们尝试利用一个转换宏将整数转换为字符串:
#define S(x) "%" #x "s"
#x
的作用是将宏参数 x
变为字符串,例如 S(123)
会被预处理器替换为 "%" "123" "s"
,根据 C99 的字面字符串连接语法,这等价于 "%123s"
。这样我们似乎可以如下使用 scanf
系列函数。
#define S(x) "%" #x "s"
#define MAXL 1023
char buf[MAXL + 1];
scanf(S(MAXL), buf);
但这是行不通的,因为 C 语言规定 #
后的宏参数不会继续展开,也就是说上面代码的最后一行等价于: scanf("%MAXLs", buf)
, 这是错误的。
要解决这个问题,我们引入一个跳板宏:
#define _S(x) "%" #x "s"
#define S(x) _S(x)
S(MAXL)
会被替换为 _S(MAXL)
,由于这里 MAXL
不在 #
后,所以会被进一步展开为 _S(1023)
,最后被转换为 "%" "1023" "s"
。这便达到了我们的预期效果,也就是我们可以这样安全地使用 scanf
系列函数:
#define _S(x) "%" #x "s"
#define S(x) _S(x)
#define MAXL 1023
char buf[MAXL + 1];
scanf(S(MAXL), buf);