缓冲区溢出警报:为什么你的 C 程序应该立刻停用 gets()?
2025-12-13
在 C 语言中,gets() 函数用于从标准输入(通常是键盘)读取一行文本,直到遇到换行符或文件结束符(EOF)为止,并将读取的字符串存储在指定的字符数组中。
注意 gets() 函数最初设计是用于标准输入的,但在文件 I/O 的背景下,我们通常讨论的是其相似的、用于文件流的函数,如 fgets()。但由于您的问题直接提到了 gets(),我们先聚焦于它,再转向更安全的文件 I/O 函数。
gets() 函数最严重、也是最常遇到的问题就是缓冲区溢出(Buffer Overflow)。
gets() 函数在读取输入时,不会检查目标缓冲区(您用来存储字符串的数组)的大小。如果用户输入了一串比您定义的数组容量更长的字符,gets() 就会毫无警告地继续向内存中写入数据,覆盖掉紧接着缓冲区后面的其他重要数据,例如局部变量、返回地址等。
程序崩溃 覆盖了返回地址可能导致程序跳转到非预期的位置,造成崩溃(Segmentation Fault)。
安全漏洞 恶意用户可以利用此漏洞执行任意代码,造成严重的安全问题。
#include
void unsafe_function() {
char buffer[10]; // 定义了一个只能存放 9 个字符(加结束符 '\0')的缓冲区
printf("请输入一串文本 (安全隐患):\n");
// gets() 不知道 buffer 的大小,如果输入超过 9 个字符就会溢出
gets(buffer);
printf("您输入的是: %s\n", buffer);
}
// int main() {
// unsafe_function();
// return 0;
// }
简而言之 由于这个严重的安全缺陷,gets() 函数在 C99 标准中已被弃用(deprecated),在 C11 标准中已被彻底移除。您应该永远避免使用 gets()。
最安全、最推荐的 gets() 替代方案是使用 fgets() 函数。fgets() 不仅可以用于文件流(FILE *),也可以安全地用于标准输入(通过使用 stdin)。
char *fgets(char *str, int size, FILE *stream);
参数说明优势str存储读取字符串的字符数组(缓冲区)-size要读取的最大字符数关键:它防止了缓冲区溢出,是安全的保障!stream文件流指针(例如:stdin 代表标准输入,或 FILE *fp 代表一个打开的文件)可以用于标准输入和文件输入如果您只是想安全地从键盘读取一行
#include
#define BUFFER_SIZE 50
void safe_input_example() {
char buffer[BUFFER_SIZE];
printf("请输入一串文本 (最大 %d 字符):\n", BUFFER_SIZE - 1);
// 使用 fgets(),最多只读取 BUFFER_SIZE - 1 个字符,确保了安全
if (fgets(buffer, BUFFER_SIZE, stdin) != NULL) {
printf("安全读取的输入是: %s", buffer);
} else {
printf("读取输入失败或遇到文件结束符。\n");
}
}
// int main() {
// safe_input_example();
// return 0;
// }
如果您需要从文件中一行一行地读取数据
#include
#define MAX_LINE 256
void safe_file_read_example() {
FILE *file_ptr;
char line[MAX_LINE];
const char *filename = "data.txt"; // 假设有一个名为 data.txt 的文件
// 尝试打开文件进行读取
file_ptr = fopen(filename, "r");
if (file_ptr == NULL) {
perror("打开文件失败");
return;
}
printf("--- 正在读取文件 (%s) ---\n", filename);
// 循环,使用 fgets() 从文件中安全地逐行读取数据
while (fgets(line, MAX_LINE, file_ptr) != NULL) {
printf("%s", line); // line 中包含读取到的行内容和换行符
}
// 关闭文件
fclose(file_ptr);
printf("--- 文件读取完成 ---\n");
}
// int main() {
// safe_file_read_example();
// return 0;
// }
与 gets() 不同,fgets() 会将读取到的换行符 \n 也存储到缓冲区中(如果缓冲区足够大)。
如果您需要去除换行符 您需要手动检查并替换它。
#include
// ... (假设 line 已经通过 fgets 安全读取) ...
// 查找换行符的位置
char *newline_pos = strchr(line, '\n');
if (newline_pos != NULL) {
// 如果找到,则将其替换为字符串结束符 '\0'
*newline_pos = '\0';
}
// 现在 line 中不再包含换行符
希望这个详细的解释和示例能帮助您理解 gets() 的危险性,并掌握更安全、更专业的 fgets() 替代方案!
c
深入理解 C 语言 roundeven:解决浮点数舍入的常见陷阱
roundeven 函数是 C 语言数学库
深入理解 C 语言 iswblank:为什么它不识别换行符?
iswblank 是 C 语言标准库
C11 并发编程:深入解析 atomic_fetch_add 的原子性与应用
atomic_fetch_add 是 C11 标准引入的原子操作之一,它用于对一个原子对象进行原子的加法操作,并返回操作前的值。原子性 (Atomicity) 确保操作是不可中断的。在一个线程执行 atomic_fetch_add 时,其他线程无法看到中间状态,保证了数据在并发环境下的一致性。
C 语言 nextafter 深度解析:浮点数边界与常见陷阱
nextafter 函数(在 C99 标准中引入,包含在
掌握 wchar_t:内存分配、安全函数与 UTF-8 替代方案
宽字符串是 C 语言中用于处理包含非 ASCII 字符(如中文、日文、特殊符号等)的文本数据类型。它们使用 wchar_t 类型来存储字符,通常比标准 char`` 大(例如 2 字节或 4 字节),并以一个特殊的宽空字符 (L'\0'`) 标记字符串的结束。
C/C++ struct tm 避坑指南:月份基准与 localtime_r 妙用
struct tm 是 C/C++ 标准库
深入理解 C 语言中的虚数单位:如何使用 _Complex_I 进行复数运算
C 语言从 C99 标准开始引入了对复数(Complex Numbers)的支持。_Complex_I 是用来表示纯虚数单位 i 的宏。要使用复数功能,您需要包含
深入理解 C 语言字符常量:陷阱、原理与正确用法
关于 C 语言的字符常量(Character constant),我将用友好、清晰的简体中文为您解释常见的陷阱(Troubles)、以及使用替代方法(Alternatives)的示例代码(Sample Code)。字符常量在 C 语言中用来表示单个字符,它们被写在单引号 ' ' 内,例如 'A'、'1'、'\n'。在 C 语言内部,一个字符常量的值实际上是它的字符集(如 ASCII 或 Unicode)中对应的整数值。
手动触发 C 语言浮点异常:feraiseexcept 的用法、陷阱与替代方案
在友好的简体中文中,我会为你详细解释这个函数的用途、常见的故障排除(以及对应的解决方案),并提供替代方法的示例代码。feraiseexcept 函数是 C 标准库
C/C++ Numerics:ilogb 故障排除指南与 IEEE 754 位操作解析
ilogb 函数是 C 标准库