10. 字符串输入输出:gets、puts、fgets、fputs 函数的安全使用
字符串的输入输出,是 C 语言里最基础也最容易踩坑的地方。我刚开始写 C 的时候,觉得不就是读个字符串嘛,能有多难?结果被 gets 坑过好几次,缓冲区溢出、程序崩溃、甚至安全漏洞……嗯,今天咱们就把这几个函数彻底聊透。
10.1 经典组合:gets 和 puts
gets 和 puts 是一对老搭档。一个负责从标准输入读字符串,一个负责往标准输出写字符串。用起来确实方便,但问题也出在这个“方便”上。
gets 函数
原型长这样:
char *gets(char *str);
它会从 stdin 读取字符,直到遇到换行符 \n 或文件结束符 EOF。然后自动把换行符替换成 \0,存到 str 指向的缓冲区里。
听起来挺正常对吧?但问题来了——它不检查缓冲区大小。你想想看,如果用户输入了 100 个字符,而你的缓冲区只分配了 50 个字节,那多出来的 50 个字符就会直接写到缓冲区后面的内存里。这就是经典的缓冲区溢出。
puts 函数
原型:
int puts(const char *str);
puts 相对安全一些。它把 str 指向的字符串输出到 stdout,然后自动加一个换行符。返回值:成功返回非负数,失败返回 EOF。
举个例子:
#include <stdio.h>
int main() {
char name[20];
printf("请输入你的名字:");
gets(name); // ❌ 危险!不检查缓冲区大小
puts(name); // ✅ 输出并自动换行
return 0;
}
这段代码能跑,但千万别学。我曾经在一个嵌入式项目里看到类似的代码,用户输入了一个超长的名字,直接导致堆栈被破坏,系统重启了。
10.2 安全替代方案:fgets 和 fputs
既然 gets 不安全,那用什么?答案是 fgets。它俩的区别,说白了就是 fgets 多了一个参数——缓冲区大小。
fgets 函数
原型:
char *fgets(char *str, int n, FILE *stream);
参数说明:
str:存放字符串的缓冲区n:最多读取n-1个字符(留一个给\0)stream:文件流,从标准输入读就传stdin
成功返回 str,失败或读到文件尾返回 NULL。
这里有个细节要注意:fgets 会保留换行符。也就是说,如果你输入了 "hello\n",它读到的字符串是 "hello\n\0"。而 gets 会把换行符吃掉。这个差异经常让人困惑。
if (str[strlen(str)-1] == '\n')
str[strlen(str)-1] = '\0';
这样后续处理就统一了。
fputs 函数
原型:
int fputs(const char *str, FILE *stream);
和 puts 的区别在于:fputs 不会自动加换行符。你需要自己决定要不要加。成功返回非负数,失败返回 EOF。
看个完整例子:
#include <stdio.h>
#include <string.h>
int main() {
char buf[50];
printf("请输入一行文字:");
if (fgets(buf, sizeof(buf), stdin) != NULL) {
// 去掉末尾的换行符
size_t len = strlen(buf);
if (len > 0 && buf[len-1] == '\n')
buf[len-1] = '\0';
printf("你输入的是:");
fputs(buf, stdout); // 不会自动换行
putchar('\n'); // 手动加换行
}
return 0;
}
10.3 四个函数的对比
我把它们整理成了一张表,方便你对照:
| 函数 | 输入/输出 | 缓冲区安全 | 保留换行符 | 自动加换行 | 推荐使用 |
|---|---|---|---|---|---|
| gets | 输入 | ❌ 不安全 | ❌ 丢弃 | — | ❌ 永不使用 |
| puts | 输出 | ✅ | — | ✅ 自动加 | ✅ 简单场景可用 |
| fgets | 输入 | ✅ 安全 | ✅ 保留 | — | ✅ 首选 |
| fputs | 输出 | ✅ | — | ❌ 不加 | ✅ 灵活控制 |
10.4 避坑指南
我这些年踩过的坑,总结成几条:
- 永远别用 gets。 我曾经在一个遗留代码里看到 gets,替换成 fgets 后,程序再也没崩溃过。
- fgets 的第二个参数要传缓冲区实际大小。 别传
sizeof(指针),那只会得到指针本身的大小(通常是 4 或 8 字节)。 - 处理 fgets 的换行符。 如果你不处理,后续的字符串比较、拼接都会出问题。
- fputs 不会自动换行。 需要换行时记得自己加
\n。
核心原则: 能用 fgets 就别用 gets,能用 fputs 就少用 puts。安全第一,方便第二。
10.5 知识体系图
下面这张图帮你理清这几个函数的关系和选择逻辑:
10.6 总结
字符串输入输出看似简单,但安全无小事。gets 已经被历史淘汰了,别再用它。fgets 和 fputs 才是你日常开发中的好搭档。记住:缓冲区大小永远要检查,换行符处理要细心,输出时换行要明确。
嗯,这些经验都是我用实际 bug 换来的。希望你读完这一章,能少走一些弯路。
公众号:蓝海资料掘金营,微信 deep3321