6. 字符串读写:fgets、fputs 与 gets 的“前世今生”

字符串读写,说白了就是按“行”干活。你想想看,我们平时处理配置文件、日志文件、甚至网络协议报文,哪个不是一行一行来的?C语言里专门干这活的,就是 fgetsfputs 这对搭档。当然,还有一个已经被“开除”的老家伙——gets。今天我就带你把这几个函数彻底捋清楚。

6.1 fputs:把字符串“扔”进文件

fputs 的用法特别直白:把字符串写到文件里,不自动加换行符。原型长这样:

int fputs(const char *str, FILE *stream);

成功返回非负数,失败返回 EOF。我习惯把它当成“文件版的 puts”,但注意,puts 会偷偷加个 \nfputs 不会。这一点坑过不少人。

个人经验: 我在项目中遇到过有人用 fputs 写日志,结果所有行都挤在一起。原因就是没手动加 \n。所以,要么你写 fputs("hello\n", fp),要么用 fprintf 带格式。

6.2 fgets:安全的“行读取器”

fgets 是 C 标准库中最值得信赖的读取函数之一。原型:

char *fgets(char *buf, int size, FILE *stream);

它最多读取 size - 1 个字符,然后自动在末尾加 \0。读到换行符也会停下,并把换行符包含在字符串里。成功返回 buf,失败或读到文件尾返回 NULL

避坑指南: 我曾经在解析 CSV 文件时,因为一行数据超过了我设定的缓冲区大小,导致数据被截断。后来我养成了一个习惯:每次用 fgets 后,检查字符串末尾是不是换行符。如果不是,说明这一行没读完,需要特殊处理。

6.3 缓冲区溢出防护:fgets 的“安全锁”

为什么说 fgets 安全?因为它明确告诉函数“我的缓冲区有多大”。你想想看,如果缓冲区只有 10 字节,而文件里有一行 100 字节的数据,fgets 只会读 9 个字符进来,剩下的留在文件流里。这就是“缓冲区溢出防护”的核心——限制写入量

我建议你永远不要用 scanf("%s", ...)gets 来读字符串。它们不知道缓冲区边界,就像闭着眼睛开车。而 fgets 就是你的安全带。

警告: 即使用了 fgets,也要注意:如果缓冲区太小,数据会被截断。你需要自己处理“行太长”的情况。我一般会用一个较大的缓冲区(比如 4096 字节),并配合 strchr 检查换行符。

6.4 gets 函数:为什么被“判死刑”?

gets 从标准输入读取一行,直到遇到换行符。但它不检查缓冲区大小。如果输入的数据超过缓冲区长度,就会发生缓冲区溢出,覆盖栈上的其他数据。这就是著名的“缓冲区溢出攻击”的入口。

C11 标准已经正式废弃了 gets。说白了,它就是一颗定时炸弹。我刚开始学 C 时,书上还教 gets,后来我亲眼看到同事的代码因为 gets 导致程序崩溃,从那以后我再也没用过它。

替代方案: 永远用 fgets(stdin, ...) 代替 gets。如果你需要去掉 fgets 读进来的换行符,可以这样:

char buf[100];
if (fgets(buf, sizeof(buf), stdin)) {
    buf[strcspn(buf, "\n")] = '\0';  // 去掉换行符
}

6.5 知识体系:字符串读写核心逻辑

下面这张图帮你理清这几个函数的关系和选择逻辑:

字符串读写函数选择逻辑 字符串读写需求 写入文件 读取文件 fputs(str, fp) 不自动加换行 fgets(buf, size, fp) 安全,限制读取长度 gets(buf) ❌ 已废弃 缓冲区溢出风险 核心原则:永远用 fgets 代替 gets,用 fputs 时注意换行符

6.6 实战对比:fgets vs gets

看一段代码你就明白了:

// 危险版本(永远不要写)
char buf[10];
gets(buf);  // 如果输入超过9个字符,直接崩溃

// 安全版本
char buf[10];
if (fgets(buf, sizeof(buf), stdin)) {
    // 最多读9个字符,安全
    printf("你输入了: %s", buf);
}

我的习惯: 在嵌入式项目中,我经常用 fgets 配合一个循环来读取完整的行。比如这样:

char line[128];
while (fgets(line, sizeof(line), fp)) {
    // 处理每一行
    if (line[strlen(line)-1] != '\n') {
        // 行太长,需要继续读取或报错
    }
}

6.7 总结一下

字符串读写其实就两件事:写用 fputs(记得加换行),读用 fgets(指定缓冲区大小)。至于 gets,就当它不存在吧。我见过太多因为 gets 导致的漏洞,真的不值得冒险。

嗯,这里要注意:fgets 会保留换行符,而 fputs 不会自动加。这两个特性配合起来,正好可以逐行读写文件,不会丢失换行信息。你想想看,是不是很巧妙?

一句话记住: 读文件用 fgets,写文件用 fputs,永远不用 gets。


公众号:蓝海资料掘金营,微信deep3321