C 库函数 – gets()(建议收藏)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

截止目前, 星球 内专栏累计输出 90w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 3100+ 小伙伴加入学习 ,欢迎点击围观

前言:为什么学习 gets() 函数?

在 C 语言编程中,输入与输出(I/O)操作是开发者必须掌握的基础技能之一。gets() 函数作为标准库中的输入函数,曾被广泛用于从标准输入(通常是键盘)读取字符串。然而,由于其设计缺陷,该函数在现代编程实践中已被弃用,并被更安全的替代方案取代。尽管如此,学习 gets() 仍然具有重要意义:它帮助开发者理解缓冲区管理的核心概念,并为深入学习安全编码原则奠定基础。

本文将从函数的基本用法、潜在风险、替代方案等角度展开,结合代码示例和实际案例,帮助读者全面理解 gets() 的特性及其实用场景。对于编程初学者,这将是一次系统化学习输入函数的机会;对于中级开发者,则能通过对比分析提升代码安全性意识。


函数原型与基本用法:像水杯一样理解缓冲区

函数原型解析

gets() 的函数原型如下:

char *gets(char *str);

该函数接受一个字符指针作为参数,其功能是从标准输入设备(如键盘)读取一行字符,直到遇到换行符(\n)或文件结束符(EOF)。读取完成后,换行符会被替换为字符串终止符 '\0',并将输入的字符串存储到 str 指向的内存地址中。

简单示例:基础用法

以下是一个典型的 gets() 使用场景:

#include <stdio.h>

int main() {
    char user_input[50];
    printf("请输入你的名字:");
    gets(user_input);
    printf("你好,%s!\n", user_input);
    return 0;
}

当运行此程序时,用户输入的内容(如“张三”)会被存储到 user_input 数组中。但需要注意:gets() 不会检查输入长度,这正是其最大的安全隐患。


常见问题与风险:当水杯被灌满时会发生什么?

缓冲区溢出的比喻

想象一个容量为 500 毫升的水杯(对应 char user_input[50])。如果倒入 1000 毫升的水(用户输入超过 49 个字符),溢出的水会污染周围环境。在编程中,这表现为缓冲区溢出——当输入数据超过目标数组的容量时,多余的数据会覆盖相邻内存区域,导致程序崩溃或被恶意利用。

案例分析:缓冲区溢出攻击演示

以下代码展示了 gets() 的危险性:

#include <stdio.h>

void read_name() {
    char name[10];  // 只能存储 9 个字符 + 终止符
    printf("输入名字:");
    gets(name);     // 没有长度限制
}

int main() {
    read_name();
    printf("输入结束!\n");  // 可能永远不会执行
    return 0;
}

若用户输入超过 9 个字符(例如“abcdefghijklmnop”),程序可能因覆盖栈中的返回地址而崩溃。在更严重的场景中,攻击者可能通过精心构造的输入控制程序执行流程,例如:

// 假设 name 数组位于内存地址 0x1000
// 攻击者输入:'A' × 14 + '\x00\x00\x10\x08'(伪造的返回地址)
// 这会导致程序跳转到攻击者指定的内存位置执行代码

为什么 gets() 被弃用?

根据 C11 标准,gets() 已被官方标记为“已弃用”,原因包括:

  1. 缺乏长度检查:无法限制输入长度,直接导致缓冲区溢出风险。
  2. 不可控的输入来源:无法指定输入源(如文件),仅支持标准输入。
  3. 安全隐患:历史上众多安全漏洞(如 Shellshock)与 gets() 直接相关。

安全替代方案:如何优雅地实现输入功能?

替代函数:fgets() 的正确使用

fgets()gets() 的安全替代函数,其原型如下:

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

参数说明:

  • str:目标字符串的首地址。
  • size:最大读取字节数(包括终止符)。
  • stream:输入源(如 stdin)。

关键区别: | 特性 | gets() | fgets() | |---------------------|----------------|----------------| | 输入长度限制 | 无 | 由 size 参数控制 | | 输入源 | 仅标准输入 | 可指定任意流 | | 安全性 | 高风险 | 安全可控 | | 返回值 | 成功返回 str | 失败或 EOF 返回 NULL |

代码示例:用 fgets() 替代 gets()

#include <stdio.h>

int main() {
    char user_input[50];
    printf("请输入你的名字:");
    fgets(user_input, sizeof(user_input), stdin);  // sizeof 自动计算数组大小
    // 处理可能存在的换行符
    user_input[strcspn(user_input, "\n")] = '\0';
    printf("你好,%s!\n", user_input);
    return 0;
}

改进点说明

  1. 输入长度控制:通过 sizeof(user_input) 确保最多读取 50 字节。
  2. 换行符处理:使用 strcspn() 移除输入中的换行符,避免后续处理问题。
  3. 输入源明确:显式指定 stdin,代码意图更清晰。

进阶实践:如何防御缓冲区溢出?

策略一:始终使用安全函数

在 C 语言中,除了 fgets(),还有以下安全函数可供选择:

  • snprintf():安全的字符串格式化输出。
  • strncpy():安全的字符串复制(需手动添加终止符)。
  • strncat():安全的字符串拼接。

策略二:静态代码分析工具

使用工具(如 Valgrind、Clang Static Analyzer)进行代码扫描,自动检测潜在的缓冲区溢出风险。

策略三:防御性编程习惯

  • 固定缓冲区大小:避免使用动态内存分配时的不确定性。
  • 输入验证:对用户输入进行格式和长度检查。
  • 错误处理:检查函数返回值,及时响应异常情况。

结论:从 gets() 看安全编程的核心原则

通过学习 gets() 函数,我们深刻认识到:安全性与便利性往往不可兼得。虽然 gets() 的简洁语法曾让开发者青睐,但其设计缺陷使其成为安全漏洞的温床。现代编程中,开发者必须遵循以下原则:

  1. 预分配资源:明确输入/输出的最大容量。
  2. 边界检查:对所有外部输入进行长度验证。
  3. 使用安全函数:优先选择提供参数控制的库函数。

尽管 gets() 已被弃用,但它依然是一个极佳的教学案例——通过理解其缺陷与改进方案,开发者能更深入地掌握 C 语言的内存管理机制,并培养出严谨的安全编码习惯。在实际开发中,我们应始终以 fgets() 等安全函数为基础,构建可靠且健壮的程序。


扩展思考:为什么某些代码仍使用 gets()?

在特定场景下(如旧代码维护或受限环境),开发者可能仍需使用 gets()。此时应采取以下措施:

  • 严格控制输入环境:确保输入来源可信且可控。
  • 手动添加长度检查:结合 strlen() 验证输入长度。
  • 设置沙箱环境:通过隔离机制限制潜在危害。

但需强调:在任何新项目中,使用 gets() 都是不可接受的。安全编码不仅是技术要求,更是对用户和社会的责任。

最新发布