位置: 文档库 > C/C++ > scanf()和gets()在C语言中的区别是什么?

scanf()和gets()在C语言中的区别是什么?

EmberRift 上传于 2021-07-28 20:58

在C语言编程中,输入操作是程序与用户交互的核心环节。标准库提供的`scanf()`和`gets()`函数常被用于处理用户输入,但二者在实现机制、安全性及适用场景上存在显著差异。本文将从底层原理、功能特性、安全隐患及实际应用等维度展开深度解析,帮助开发者理解如何根据需求选择合适的输入方法。

一、函数原型与基本用法

`scanf()`和`gets()`均属于标准输入库(``),但函数签名与调用方式截然不同:

#include 

int main() {
    // scanf()示例:格式化读取
    char str1[20];
    int num;
    printf("输入字符串和数字(用空格分隔): ");
    scanf("%s %d", str1, &num);
    printf("读取结果: %s, %d\n", str1, num);

    // gets()示例:读取整行
    char str2[20];
    printf("输入一行文本: ");
    gets(str2);  // 已废弃,仅作演示
    printf("读取结果: %s\n", str2);
    return 0;
}

从代码可见,`scanf()`通过格式字符串(如`%s`、`%d`)控制输入类型,而`gets()`直接读取整行(直到换行符)。这种差异直接影响了二者的功能边界。

二、核心区别解析

1. 输入控制方式

`scanf()`的格式化特性使其能精确控制输入类型:

  • 类型匹配:通过`%d`、`%f`等转换说明符确保数据类型正确。
  • 分隔符处理:默认以空白符(空格、制表符、换行)分隔输入项。
  • 返回值**:返回成功匹配的输入项数,便于错误检测。
int count = scanf("%d %f", &a, &b);
if (count != 2) {
    printf("输入格式错误\n");
}

相比之下,`gets()`是“无脑”读取:

  • 不检查数据类型,所有输入均视为字符序列。
  • 以换行符(`\n`)为终止条件,换行符本身不存入缓冲区。
  • 无返回值指示成功与否(始终返回指针或`NULL`)。

2. 缓冲区溢出风险

安全性是二者最关键的差异。`gets()`无法指定缓冲区大小,攻击者可输入超长字符串覆盖栈内存:

// 恶意输入示例
char buf[10];
gets(buf);  // 输入"AAAAAAAAAAAAAAAAAAAA"可能导致溢出

这种漏洞在CTF竞赛和真实攻击中屡见不鲜。而`scanf()`通过字段宽度限制可部分缓解问题:

char safe_buf[10];
scanf("%9s", safe_buf);  // 最多读取9个字符

但需注意,`%s`仍会忽略前导空白符,且字段宽度仅对`%s`、`%[]`等字符串转换有效。

3. 换行符处理

输入流中的换行符会导致行为差异:

  • `scanf()`读取后,换行符可能残留在输入缓冲区,影响后续读取。
  • `gets()`会消耗并丢弃换行符,但若前次输入残留换行符(如`scanf()`后未清空缓冲区),会导致`gets()`直接读取空行。
// 问题复现示例
char str[20];
int num;

printf("输入数字: ");
scanf("%d", &num);  // 用户输入"123\n",换行符残留

printf("输入字符串: ");
gets(str);  // 直接读取残留的换行符,str为空

4. 混合输入的复杂性

当程序需要交替读取数字和字符串时,`scanf()`与`gets()`的组合极易出错。解决方案包括:

  • 使用`scanf()`的`%*c`清空缓冲区:
scanf("%d%*c", &num);  // 读取数字并丢弃后续字符(包括换行符)
  • 统一使用`fgets()`替代`gets()`,配合`sscanf()`解析:
char line[100];
fgets(line, sizeof(line), stdin);  // 安全读取整行
sscanf(line, "%d", &num);  // 从行中解析数字

三、现代C语言的替代方案

鉴于`gets()`的严重安全隐患,C11标准已将其移除,推荐使用以下替代方法:

1. `fgets()`函数

语法:

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

优势:

  • 明确指定最大读取长度(`n-1`个字符 + 终止符)。
  • 保留换行符(若缓冲区足够大)。
  • 返回`NULL`表示错误或EOF。
char buf[50];
if (fgets(buf, sizeof(buf), stdin) != NULL) {
    // 处理输入(可能包含换行符)
    buf[strcspn(buf, "\n")] = '\0';  // 手动移除换行符
}

2. `getline()`函数(POSIX扩展)

动态分配内存的更安全方案:

#include 
#include 

int main() {
    char *line = NULL;
    size_t len = 0;
    ssize_t read;

    printf("输入: ");
    read = getline(&line, &len, stdin);
    if (read != -1) {
        printf("读取: %s", line);
    }
    free(line);  // 必须释放内存
    return 0;
}

优点:自动处理内存分配,适合未知长度的输入。

四、实际应用场景对比

场景 `scanf()`适用性 `gets()`/`fgets()`适用性
读取格式化数据(如数字+字符串) ✅ 理想选择 ❌ 需额外解析
读取未知长度的行 ❌ 容易出错 ✅ `fgets()`或`getline()`
需要严格输入验证 ✅ 可配合返回值检查 ❌ 需手动处理
处理含空格的字符串 ❌ `%s`遇空格终止 ✅ 读取整行

五、安全编程实践

遵循以下原则可避免大多数输入相关漏洞:

  1. 禁用`gets()`:C11已移除,编译器通常会产生警告。
  2. 限制`scanf()`的字段宽度:如`%19s`防止20字节缓冲区溢出。
  3. 清空输入缓冲区:在`scanf()`后使用循环丢弃剩余字符。
  4. 优先使用`fgets()`:结合`sscanf()`实现安全解析。
// 安全输入示例
void safe_input(char *buffer, size_t size) {
    if (fgets(buffer, size, stdin) == NULL) {
        // 处理错误
        return;
    }
    // 移除可能的换行符
    size_t len = strlen(buffer);
    if (len > 0 && buffer[len - 1] == '\n') {
        buffer[len - 1] = '\0';
    }
}

六、C++中的替代方案

C++程序员应优先使用``和``:

#include 
#include 
#include 

int main() {
    // 安全读取字符串
    std::string str;
    std::cout > num)) {
        std::cin.clear();
        std::cin.ignore(std::numeric_limits<:streamsize>::max(), '\n');
        std::cout 

优势:

  • 类型安全:`std::cin`自动处理类型转换。
  • 异常处理:可通过流状态检测错误。
  • 无需手动管理内存:`std::string`动态扩展。

关键词C语言输入函数、scanf()、gets()、缓冲区溢出、fgets()、安全编程格式化输入、输入验证

简介:本文详细对比了C语言中`scanf()`和`gets()`在输入控制、安全性、换行符处理等方面的差异,分析了缓冲区溢出等安全隐患,并推荐了`fgets()`、`getline()`等现代替代方案,同时提供了C++中的安全输入实践。