在 C 语言中读取字符串是一件非常危险的事情。当读取用户输入时,程序员可能会尝试使用 C 标准库中的 gets
函数。它的用法非常简单:
char *gets(char *string);
gets()
从标准输入读取数据,然后将结果存储在一个字符串变量中。它会返回一个指向字符串的指针,如果没有读取到内容,返回 NULL
值。
举一个简单的例子,我们可能会问用户一个问题,然后将结果读入字符串中:
#include
#include
int main()
{
char city[10]; // 例如 "Chicago"
// 这种方法很糟糕 .. 不要使用 gets
puts("Where do you live?");
gets(city);
printf(" is length %ldn", city, strlen(city));
return 0;
}
输入一个相对较短的值就可以:
Where do you live?
Chicago
is length 7
然而,gets()
函数非常简单,它会天真地读取数据,直到它认为用户完成为止。但是它不会检查字符串是否足够容纳用户的输入。输入一个非常长的值会导致 gets()
存储的数据超出字符串变量长度,从而导致覆盖其他部分内存。
Where do you live?
Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch
is length 58
Segmentation fault (core dumped)
最好的情况是,覆盖部分只会破坏程序。最坏的情况是,这会引入一个严重的安全漏洞,恶意用户可以通过你的程序将任意数据插入计算机的内存中。
这就是为什么在程序中使用 gets()
函数是危险的。使用 gets()
,你无法控制程序尝试从用户读取多少数据,这通常会导致缓冲区溢出。
安全的方法
fgets()
函数历来是安全读取字符串的推荐方法。此版本的 gets()
提供了一个安全检查,通过仅读取作为函数参数传递的特定数量的字符:
char *fgets(char *string, int size, FILE *stream);
fgets()
函数会从文件指针读取数据,然后将数据存储到字符串变量中,但最多只能达到 size
指定的长度。我们可以更新示例程序来测试这一点,使用 fgets()
而不是 gets()
:
#include
#include
int main()
{
char city[10]; // 例如 "Chicago"
puts("Where do you live?");
// fgets 虽好但是并不完美
fgets(city, 10, stdin);
printf(" is length %ldn", city, strlen(city));
return 0;
}
如果编译运行,你可以在提示符后输入任意长的城市名称。但是,程序只会读取 size
= 10 数据存储到字符串变量中。因为 C 语言在字符串末尾会添加一个空(