c语言nullgets

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

NULL

NULL 是 C 语言中的一个(Macro),它的值通常是整数 0 或者 (void *)0

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

核心概念

NULL 的核心作用是表示一个“空指针”“无效指针”,它不是一个指向有效内存地址的指针,而是一个特殊的值,用来表示“这里没有东西”或“这个指针不指向任何地方”。

定义和使用

NULL 通常在标准头文件 <stdio.h><stddef.h> 中被定义,你可以像这样使用它:

#include <stdio.h>
int main() {
    // 1. 指向整型的指针
    int *ptr_to_int = NULL; // 声明一个整型指针,并初始化为NULL,表示它目前不指向任何整数
    // 在使用指针前,必须检查它是否为NULL
    if (ptr_to_int == NULL) {
        printf("指针 ptr_to_int 是 NULL,不能解引用,\n");
        // *ptr_to_int = 100; // 如果取消注释,会导致程序崩溃(段错误)
    }
    // 2. 指向字符的指针(常用于字符串)
    char *name = NULL; // 声明一个字符指针,初始化为NULL
    if (name == NULL) {
        printf("指针 name 是 NULL,\n");
    }
    // 3. 函数返回值
    // 很多函数在失败时会返回NULL,fopen()
    FILE *file = fopen("non_existent_file.txt", "r");
    if (file == NULL) {
        printf("无法打开文件,可能文件不存在或没有权限,\n");
        // perror("fopen"); // 会打印更详细的错误信息
        return 1; // 返回非零表示错误
    }
    // ... 使用文件 ...
    fclose(file);
    return 0;
}

NULL

  • 是什么:一个表示“空”或“无效”的宏,值为 0
  • 为什么用
    • 初始化指针:在声明指针时,将其初始化为 NULL 是一个好习惯,可以避免使用未初始化的“野指针”。
    • 错误检查:函数在无法返回有效指针时(如内存分配失败、文件打开失败),会返回 NULL,调用者必须检查返回值是否为 NULL 来判断操作是否成功。
    • 终止条件:在链表等数据结构中,常用 NULL 表示链表的末尾。

gets

gets 是一个标准 C 库函数,用于从标准输入(通常是键盘)读取一行字符串,直到遇到换行符或文件结束符(EOF),然后将该字符串(不包含换行符)存入指定的缓冲区。

函数原型

char *gets(char *str);
  • str: 一个指向字符数组的指针,也就是缓冲区的地址。
  • 返回值:如果成功读取,返回 str(指向缓冲区的指针);如果到达文件末尾或发生读取错误,返回 NULL

gets 的工作方式(极其危险)

gets 的致命缺陷在于:它不知道目标缓冲区有多大,它会盲目地读取输入,直到遇到换行符或 EOF,然后将所有读入的内容(包括可能超出缓冲区容量的部分)写入 str 指向的内存区域。

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

这会导致缓冲区溢出(Buffer Overflow)

gets 的危险示例

#include <stdio.h>
int main() {
    char name[10]; // 只能容纳 9 个字符 + 1 个字符串结束符 '\0'
    printf("请输入你的名字(少于10个字符): ");
    gets(name); // 危险!用户输入超过9个字符,就会溢出
    printf("你好, %s\n", name);
    return 0;
}

如果用户输入 Alexander (9个字符): 程序正常工作,name 数组被正确填充。

如果用户输入 Johann Sebastian Bach (超过9个字符): gets 会把所有字符都写入 name 数组所在的内存空间,它会覆盖掉 name 数组后面的内存,这些内存可能存储着其他变量、函数返回地址等关键信息,这会导致:

  • 程序崩溃(段错误)。
  • 数据被破坏
  • 最严重的情况:攻击者可以精心构造一段输入,覆盖函数的返回地址,使其指向恶意代码,从而执行任意代码(这是许多安全漏洞的根源)。

为什么 gets 被废弃?

正是因为 gets 存在如此严重且难以避免的安全漏洞,C 标准委员会已经明确废弃了它。

  • 在 C99 标准中,gets 被标记为“删除(removed)”。
  • 在 C11 标准中,它被完全移除。
  • 在现代的 C 编译器(如 GCC, Clang)中,如果你使用 gets,编译器会直接给出警告或错误

NULLgets 的关系

NULLgets 本身没有直接的语法关系,但它们通过函数的返回值联系在一起。

gets 函数在两种情况下会返回 NULL

  1. 读取到文件结束符:在 Linux/macOS 中按下 Ctrl+D,或者在 Windows 中按下 Ctrl+Z 再按回车。
  2. 发生读取错误:输入流出错。

一个包含 NULL 检查的 gets 示例(尽管 gets 仍然不安全)

#include <stdio.h>
int main() {
    char buffer[100];
    printf("请输入一些内容,然后按回车: ");
    // 检查 gets 的返回值是否为 NULL
    if (gets(buffer) == NULL) {
        printf("读取输入失败或遇到文件结束,\n");
        return 1;
    }
    printf("你输入的内容是: %s\n", buffer);
    return 0;
}

这个例子虽然检查了 NULL,但仍然不安全! 因为 NULL 检查只能处理“没有输入”或“输入错误”的情况,完全无法防止缓冲区溢出,用户只要输入超过 99 个字符,程序依然会崩溃或被攻击。


安全的替代方案:fgets

既然 gets 不能用,我们应该用什么?答案是 fgets

fgets 是一个更安全、更可控的函数。

函数原型

char *fgets(char *str, int n, FILE *stream);
  • str: 目标缓冲区指针。
  • n: 要读取的最大字符数(包括字符串结束符 \0),这是 gets 所没有的安全机制!
  • stream: 输入流,通常是 stdin(标准输入)。

fgets 的工作方式

fgets 会从 stream 中读取最多 n-1 个字符,或者直到遇到换行符或 EOF,它总是会在读取的字符串末尾添加一个 \0

fgets 的优点

  1. 防止缓冲区溢出:通过指定 nfgets 保证不会写入超过 n-1 个字符,从根本上杜绝了缓冲区溢出的风险。
  2. 保留换行符:如果输入行不超过 n-1 个字符,fgets 会将换行符 \n 也读入缓冲区,这有时很有用,有时需要手动去掉。

使用 fgets 的安全示例

#include <stdio.h>
#include <string.h> // 用于 strlen
int main() {
    char name[10]; // 缓冲区大小仍然是10
    printf("请输入你的名字: ");
    // 使用 fgets,并指定缓冲区大小 sizeof(name)
    // sizeof(name) 的值是 10
    if (fgets(name, sizeof(name), stdin) == NULL) {
        printf("读取输入失败,\n");
        return 1;
    }
    // fgets 可能会把换行符也读进来,我们需要去掉它
    size_t len = strlen(name);
    if (len > 0 && name[len - 1] == '\n') {
        name[len - 1] = '\0'; // 将换行符替换为字符串结束符
    }
    printf("你好, %s\n", name);
    return 0;
}

即使用户输入 Johann Sebastian Bachfgets 也只会读取前 9 个字符(Johann S),然后自动在第 10 个位置放上 \0,程序绝对安全!


总结与对比

特性 gets (已废弃) fgets (推荐)
安全性 极不安全,会导致缓冲区溢出 安全,通过指定最大长度防止溢出
缓冲区大小 不关心,完全由用户输入决定 必须指定,由程序员控制
换行符处理 自动丢弃换行符 会保留换行符(如果缓冲区足够)
返回值 成功返回 str,失败返回 NULL 成功返回 str,失败或到达 EOF 返回 NULL
标准状态 C99 已删除,C11 已移除 C 标准库的一部分,安全可靠

黄金法则: 在任何现代 C 语言编程中,永远不要使用 gets,当你需要从标准输入读取一行字符串时,总是使用 fgets,并记得传入缓冲区的大小 sizeof(buffer),养成检查函数返回值(包括 NULL)的好习惯。

-- 展开阅读全文 --
头像
dede图片名怎么调用?
« 上一篇 04-20
cprintf函数如何在C语言中实现格式化输出?
下一篇 » 04-20

相关文章

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

目录[+]