gets 函数的核心作用
gets 函数的全称是 "get string"(获取字符串),它的唯一作用就是从标准输入流(通常是键盘)中读取一行字符,直到遇到换行符 \n 或者文件结束符 EOF,然后将这些字符(不包括换行符)存入一个字符数组(字符串)中,并在末尾自动添加一个字符串结束符 \0。

(图片来源网络,侵删)
函数原型
#include <stdio.h> char *gets(char *str);
- 参数:
char *str是一个指向字符数组的指针,这个数组必须有足够的空间来存储用户输入的字符串。 - 返回值:
- 成功时,返回指向
str的指针。 - 如果读取失败(例如遇到文件结束符
EOF),或者str是NULL指针,则返回NULL。
- 成功时,返回指向
gets 的工作原理与示例
gets 的工作方式非常简单直接:它会一直等待用户输入,直到用户按下回车键,回车键产生的换行符 \n 会被 gets 丢弃,取而代之的是 \0。
示例代码
#include <stdio.h>
int main() {
char name[50]; // 定义一个字符数组来存储名字
printf("请输入你的名字: ");
gets(name); // 从键盘读取一行并存入 name 数组
printf("你好, %s!\n", name); // 打印出读取到的名字
return 0;
代码解析
char name[50];:我们定义了一个大小为50的字符数组name,用于存储用户输入的字符串。printf("请输入你的名字: ");:在屏幕上显示提示信息。gets(name);:程序在这里暂停,等待用户输入,假设用户输入Alice并按下回车。gets会将A,l,i,c,e这五个字符依次存入name数组。- 它会丢弃回车符
\n。 - 然后在
name数组的第6个位置(索引为5)添加一个\0,表示字符串结束。
printf("你好, %s!\n", name);:%s格式说明符会从name数组的第一个字符开始打印,直到遇到\0为止,屏幕上会显示你好, Alice!。
gets 的致命缺陷:缓冲区溢出
尽管 gets 使用简单,但它被认为是C语言中最危险、最不安全的函数之一,因此在C11标准中被正式移除,在较新的编译器(如GCC, Clang)中,直接使用 gets 会收到编译警告或错误。
什么是缓冲区溢出?
gets 函数不会检查输入的字符串长度,它只会傻傻地一直往你提供的数组里写入字符,直到遇到换行符或 EOF,如果用户输入的字符数量超过了数组的容量,多出来的字符就会溢出数组,覆盖掉内存中数组后面的数据,这会导致严重的后果。
缓冲区溢出的危害
- 程序崩溃:如果覆盖了程序的关键数据或代码,可能导致程序异常终止。
- 数据被破坏:如果覆盖了其他重要变量的数据,会导致程序逻辑错误。
- 安全漏洞:这是最危险的一点,攻击者可以精心构造一段超长的输入,利用缓冲区溢出覆盖函数的返回地址,从而执行任意代码,完全控制你的程序,这是历史上无数网络攻击的根源。
一个危险的示例
#include <stdio.h>
int main() {
char password[10]; // 假设密码最大长度为9
printf("请输入密码: ");
gets(password); // 危险!没有检查输入长度
printf("密码已设置,\n");
return 0;
}
攻击场景:

(图片来源网络,侵删)
- 程序期望用户输入一个不超过9个字符的密码。
- 攻击者输入一个超过10个字符的字符串,
1234567890123456。 gets会把这16个字符全部写入password数组。- 数组只有10个字节大小,后面的6个字节会溢出,覆盖掉
main函数栈帧中的其他数据,比如函数的返回地址。 - 当
main函数执行完毕试图返回时,它会跳转到被恶意篡改的地址,导致程序执行攻击者预设的恶意代码。
安全的替代方案
由于 gets 极其危险,绝对不要在任何新代码中使用它,你应该使用更安全的替代函数。
fgets - 推荐首选
fgets 是最常用、最安全的替代方案。
- 作用:从指定的流(如
stdin标准输入)中读取一行。 - 关键优势:它可以限制读取的最大字符数,从而有效防止缓冲区溢出。
函数原型:
char *fgets(char *str, int n, FILE *stream);
str: 存储字符串的字符数组。n: 最多读取的字符数(包括\0)。这是一个非常重要的安全边界。stream: 要读取的流,stdin代表标准输入(键盘)。
与 gets 的区别:

(图片来源网络,侵删)
fgets会保留换行符\n(如果读取成功且空间足够)。fgets需要指定最大读取长度。
示例:
#include <stdio.h>
int main() {
char name[50];
printf("请输入你的名字: ");
fgets(name, sizeof(name), stdin); // 安全!最多读取 49 个字符 + 1 个 '\0'
// 去掉 fgets 读取到的换行符
for (int i = 0; name[i] != '\0'; i++) {
if (name[i] == '\n') {
name[i] = '\0';
break;
}
}
printf("你好, %s!\n", name);
return 0;
}
scanf
scanf 也可以用来读取字符串,但用法和限制需要特别注意。
- 作用:根据格式字符串从输入中读取数据。
- 用法:
scanf("%49s", name);(注意这里的49)。 - 关键点:
scanf遇到空格、制表符、换行符就会停止读取。scanf无法读取整行,只能读取一个单词。- 同样需要指定最大读取宽度(如
%49s)来防止溢出,因为name数组大小是50,要留一个位置给\0。
示例:
#include <stdio.h>
int main() {
char name[50];
printf("请输入你的名字(单个单词): ");
scanf("%49s", name); // 安全,但只能读取一个单词
printf("你好, %s!\n", name);
return 0;
}
| 特性 | gets |
fgets (推荐) |
scanf |
|---|---|---|---|
| 作用 | 从标准输入读取一行 | 从指定流读取一行 | 根据格式从输入读取数据 |
| 安全性 | 极不安全,易导致缓冲区溢出 | 安全,可限制读取长度 | 相对安全,但需注意宽度 |
| 换行符处理 | 丢弃 \n |
保留 \n |
遇到 \n 停止读取 |
| 能否读取空格 | 能 | 能 | 不能,遇到空格停止 |
| 参数 | gets(str) |
fgets(str, n, stream) |
scanf("%s", str) |
| 现代标准 | C11已移除 | 标准库函数 | 标准库函数 |
核心结论:
永远不要使用 gets 函数。 它是一个历史遗留的、充满安全漏洞的函数,在任何现代C语言项目中,都应该使用 fgets 来安全地读取一行输入。
