fgets与gets有何区别?

99ANYc3cd6
预计阅读时长 17 分钟
位置: 首页 C语言 正文

核心思想:一句话总结

  • gets() 是“危险的”,因为它无法限制读取的字符数,极易导致缓冲区溢出,从而引发程序崩溃或安全漏洞。在现代 C 编程中,应绝对避免使用 gets()
  • fgets() 是“安全的”,因为它允许你指定最大读取字符数,可以有效地防止缓冲区溢出,是读取字符串的标准和推荐方法。

gets() 函数 (不安全 - 已废弃)

gets 函数的名字是 "get string"(获取字符串)的缩写。

c语言 fgets gets
(图片来源网络,侵删)

函数原型

char *gets(char *str);

功能

从标准输入(通常是键盘)读取一行字符,直到遇到换行符 \n 或文件结束符 EOF,它会读取换行符 \n,但不会将其存储到字符串中,它会自动在字符串末尾添加一个空字符 '\0' 来表示字符串结束。

参数

  • char *str: 一个指向字符数组的指针,用于存储读取到的字符串。

返回值

  • 成功时,返回指向 str 的指针。
  • 如果读取失败(例如遇到文件结束符 EOF),则返回 NULL 指针。
  • 如果一开始就遇到 EOFstr 指向的内容不会被修改。

为什么 gets() 是危险的?

gets() 的最大、也是最致命的缺陷是:它不知道目标缓冲区有多大,它会一直读取,直到遇到换行符或 EOF,完全不考虑 str 指向的数组是否装得下这么多字符。

这会导致缓冲区溢出

缓冲区溢出的后果:

c语言 fgets gets
(图片来源网络,侵删)
  1. 程序崩溃:如果写入的数据超出了分配的内存空间,可能会覆盖掉其他重要数据(如函数返回地址),导致程序异常终止。
  2. 安全漏洞:这是最严重的问题,恶意用户可以利用这个漏洞,向程序中注入并执行恶意代码,通过精心构造的超长输入,覆盖函数的返回地址,使其指向恶意代码所在的内存位置。

gets() 的使用示例 (危险!)

#include <stdio.h>
int main() {
    char name[10]; // 定义一个只能存放9个字符+1个'\0'的缓冲区
    printf("请输入你的名字: ");
    gets(name); // 危险!用户输入超过9个字符就会导致溢出
    printf("你好, %s\n", name);
    return 0;
}

如果用户输入 Alexander (9个字符)

  • 程序正常工作。
  • name 数组内容为 'A','l','e','x','a','n','d','e','r','\0'

如果用户输入 Johann Sebastian Bach (超过9个字符)

  • gets() 会将所有字符(包括空格)读入 name 数组,直到遇到换行符。
  • 它会无情地写入 name[10], name[11]... 等等,这些内存并不属于 name 数组。
  • 结果就是缓冲区溢出,程序行为不可预测,可能崩溃。

注意:GCC 编译器在编译使用 gets() 的代码时,会直接给出警告: warning: 'gets' is deprecated and not portable [-Wdeprecated-declarations] 这是在强烈警告你不要使用它。


fgets() 函数 (安全 - 推荐)

fgets 函数的名字是 "file get string"(文件获取字符串)的缩写,虽然名字里有 "file",但它也可以用于标准输入。

c语言 fgets gets
(图片来源网络,侵删)

函数原型

char *fgets(char *str, int n, FILE *stream);

功能

从指定的输入流(可以是文件 stdin,即标准输入)中读取一行字符,或者读取最多 n-1 个字符。

参数

  1. char *str: 一个指向字符数组的指针,用于存储读取到的字符串。
  2. int n: 要读取的最大字符数(包括结尾的空字符 '\0')。这是 fgets 安全的关键
  3. FILE *stream: 输入流,当要从键盘读取时,使用 stdin

返回值

  • 成功时,返回指向 str 的指针。
  • 如果读取失败或遇到文件结束符 EOF,则返回 NULL 指针。

fgets() 的安全机制

fgets 的第二个参数 n 限制了它最多只会读取 n-1 个字符,它会在以下情况之一发生时停止读取:

  1. 读取了 n-1 个字符。
  2. 读取到换行符 \n
  3. 到达文件结束符 EOF

如果读取到换行符 \nfgets 会将换行符 \n 也存储到字符串中,它在最后添加一个空字符 '\0'

fgets() 的使用示例 (安全!)

#include <stdio.h>
#include <string.h> // 用于 strlen()
int main() {
    char name[10]; // 同样定义一个只能存放9个字符+1个'\0'的缓冲区
    printf("请输入你的名字: ");
    // 安全!最多读取 sizeof(name) - 1 个字符,防止溢出
    fgets(name, sizeof(name), stdin);
    // 问题:fgets会把换行符'\n'也读进来,我们需要去掉它
    size_t len = strlen(name);
    if (len > 0 && name[len - 1] == '\n') {
        name[len - 1] = '\0'; // 将换行符替换为字符串结束符
    }
    printf("你好, %s\n", name);
    return 0;
}

分析上面的代码:

  • sizeof(name) 的值是 10。
  • fgets(name, 10, stdin) 告诉函数:最多从 stdin 读取 9 个字符(因为第10个位置要留给 '\0')。
  • 如果用户输入 Alexander (9个字符)
    • fgets 会读取全部 9 个字符,然后在后面加 '\0'\n 没有被读取
    • name 数组内容为 'A','l','e','x','a','n','d','e','r','\0'
    • strlen(name) 返回 9。name[8]'r',不是 '\n'if 条件不成立,字符串不变。
  • 如果用户输入 Johann Sebastian Bach (超过9个字符)
    • fgets 会读取前 9 个字符,然后在后面加 '\0'\n 也没有被读取
    • name 数组内容为 'J','o','h','a','n','n',' ','S','\0'
    • 程序安全,没有溢出。

处理换行符 \n 的小技巧: 上面的 if 判断是标准做法,你也可以用一个更简洁的库函数 strcspn

#include <stdio.h>
#include <string.h>
int main() {
    char name[10];
    printf("请输入你的名字: ");
    fgets(name, sizeof(name), stdin);
    // strcspn 找到 name 中第一个出现 '\n' 的位置,并返回其索引
    // 如果没找到,返回字符串的长度
    name[strcspn(name, "\n")] = '\0';
    printf("你好, %s\n", name);
    return 0;
}

gets()fgets() 的详细对比

特性 gets() fgets()
安全性 不安全,极易导致缓冲区溢出 安全,通过参数 n 限制读取长度
缓冲区大小 不知道,由程序员自己保证 明确知道,由参数 n 指定
换行符 \n 不保留,读取后丢弃 保留,作为字符串的一部分存入
输入源 只能是标准输入 stdin 可以是任何文件流,如 stdin, fp
参数个数 1个 (char *str) 3个 (char *str, int n, FILE *stream)
现代标准 C99 标准已废弃,从 C11 开始正式移除 C89 标准引入,所有现代标准都支持
推荐度 绝对不要使用 强烈推荐,读取字符串的标准方法

如何替代 gets()? (完整示例)

假设你的旧代码是这样:

// 旧代码 (危险)
char buffer[100];
gets(buffer);

你应该用 fgets() 来替换它,并处理掉多余的换行符。

#include <stdio.h>
#include <string.h>
#define BUFFER_SIZE 100
int main() {
    char buffer[BUFFER_SIZE];
    printf("请输入一行文字: ");
    // 1. 使用 fgets 安全地读取
    fgets(buffer, BUFFER_SIZE, stdin);
    // 2. 去掉 fgets 读取的换行符 '\n'
    //    注意:如果输入的字符数正好等于 BUFFER_SIZE - 1,'\n' 可能不会被读入
    //    所以检查一下是否存在 '\n' 是一个好习惯
    size_t len = strlen(buffer);
    if (len > 0 && buffer[len - 1] == '\n') {
        buffer[len - 1] = '\0';
    }
    printf("你输入的是: \"%s\"\n", buffer);
    return 0;
}

记住这个核心原则:

永远不要使用 gets() 无论是在什么情况下,无论你的代码看起来多么简单,都不要用它。

总是优先使用 fgets() 来从标准输入读取字符串。 记得要指定缓冲区的大小(sizeof(你的数组)),并且记得处理掉它可能读入的换行符 \n

这是编写健壮、安全 C 程序的基本功。

-- 展开阅读全文 --
头像
dede幼儿园模板下载安全吗?
« 上一篇 02-08
Linux C语言如何使用curl?
下一篇 » 02-08

相关文章

取消
微信二维码
支付宝二维码

目录[+]