标准IO:打开关闭、错误处理

标准IO:打开关闭、错误处理

1. 文件打开与关闭的基本概念

  • 打开(Open):占用系统资源(如文件、设备等)。
  • 关闭(Close):释放已占用的资源,避免其他程序无法访问。
  • 在操作系统底层(文件描述符层面),通过 open()close() 实现资源的占用与释放。
  • 在 C 标准库中,使用 FILE* 流指针进行操作,接口形式不同但本质一致。

重要提醒:程序结束时系统会自动释放资源,但这不是良好编程习惯。应主动调用关闭函数以确保资源及时释放,防止资源泄漏或被恶意利用。


2. 标准库中的 fopen() 函数

基本信息

  • 头文件<stdio.h>
  • 函数原型
    FILE *fopen(const char *pathname, const char *mode);
    
  • 参数说明
    • pathname:文件路径(字符串形式)
    • mode:打开模式(字符串),决定对文件的操作权限和行为
  • 返回值:成功返回 FILE* 指针;失败返回 NULL

注意fopen() 只有两个参数,不像系统调用 open() 那样有第三个权限参数(用于创建文件时指定权限)。


3. 文件打开模式详解

模式 含义 文件必须存在? 是否清空内容? 是否可读? 是否可写? 文件不存在时行为
"r" 只读 ✅ 是 ❌ 否 打开失败
"r+" 读写 ✅ 是 ❌ 否 打开失败
"w" 只写 ❌ 否 ✅ 是 自动创建新文件
"w+" 读写 ❌ 否 ✅ 是 自动创建新文件
"a" 追加写 ❌ 否 ❌ 否 自动创建新文件
"a+" 追加读写 ❌ 否 ❌ 否 自动创建新文件

关键区别说明:

  • "r" / "r+":要求文件必须存在,否则 fopen() 返回 NULL
  • "w" / "w+"
    • 若文件存在 → 清空内容后从头开始写入。
    • 若文件不存在 → 创建新文件。
  • "a" / "a+"
    • 写入操作始终追加到文件末尾(即使使用 fwritefprintf)。
    • 不会清空已有内容。
    • 支持读操作("a+"),但写操作仍只能在末尾。

实践验证

  • 使用 "r" 打开不存在的 test.txt → 失败。
  • 使用 "w" 打开不存在的 test.txt → 成功并创建文件。
  • test.txt 原有内容为 "hello",用 "w" 打开后内容被清空。
  • 若用 "a" 打开,原有内容保留,后续写入追加至末尾。

4. fopen() 与系统调用 open() 的对比

特性 fopen()(标准库) open()(系统调用)
接口层级 高层(带缓冲) 底层(无缓冲)
参数数量 2 个 至少 2 个,创建文件时需第 3 个(权限)
文件不存在时创建 由模式隐式控制(如 "w""a" 需显式传 O_CREAT 标志 + 权限参数
返回类型 FILE*(流指针) int(文件描述符)
缓冲机制 有(提升性能) 无(直接系统调用)

对应关系示例

  • "r"open(path, O_RDONLY)
  • "w"open(path, O_WRONLY | O_CREAT | O_TRUNC, 0664)
  • "a"open(path, O_WRONLY | O_CREAT | O_APPEND, 0664)

5. 文件关闭:fclose()

  • 头文件<stdio.h>
  • 函数原型
    int fclose(FILE *stream);
    
  • 功能
    • 刷新缓冲区(将未写入的数据同步到磁盘)
    • 释放 FILE* 结构体内存
    • 关闭底层文件描述符
  • 返回值
    • 成功:0
    • 失败:EOF(通常为 -1

最佳实践:每次 fopen() 后都应配对调用 fclose(),即使程序即将退出。


6. 错误处理机制

C 标准库通过全局变量 errno(全称:error number)记录最近一次系统或库函数调用的错误码。

6.1 errno 特性

  • 类型int
  • 作用域:全局变量(定义在 <errno.h> 中)
  • 行为
    • 成功时通常为 0
    • 失败时被设为特定错误码(如 2 表示 “No such file or directory”)
    • 每次调用可能覆盖,因此应在出错后立即检查

6.2 错误信息输出函数

(1) perror(const char *s)

  • 头文件<stdio.h>
  • 功能:自动读取当前 errno,输出格式为:
    s: 错误描述文本
    
  • 示例
    FILE *fp = fopen("nonexist.txt", "r");
    if (fp == NULL) {
        perror("fopen failed");  // 输出:fopen failed: No such file or directory
    }
    

(2) strerror(int errnum)

  • 头文件<string.h>
  • 功能:根据错误码返回对应的错误描述字符串(char*
  • 示例
    #include <errno.h>
    #include <string.h>
    
    FILE *fp = fopen("nonexist.txt", "r");
    if (fp == NULL) {
        printf("Error %d: %s\n", errno, strerror(errno));
        // 输出:Error 2: No such file or directory
    }
    

对比

  • perror() 更简洁,适合快速调试。
  • strerror() 更灵活,可嵌入自定义日志格式。

7. 文件权限与 umask

7.1 默认文件权限

  • 当使用 "w""a" 创建新文件时,标准库默认使用权限 0664(八进制)。
  • 实际权限 = 指定权限 & ~umask
    • 例如:若 umask = 0022,则 0666 & ~0022 = 0644
  • 常见 umask 值
    • 普通用户:0022 → 新文件权限为 rw-r--r--(644)
    • root 用户:00220002

7.2 权限表示法

  • 符号表示rwxrwxrwx(读=4,写=2,执行=1)
  • 数值表示:八进制数,如 06440755
    • 注意:C 代码中应写作 0664(前导零表示八进制)

提示:可通过 umask 000 临时禁用掩码,使创建的文件完全按指定权限设置。


8. 完整代码示例

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

int main() {
    FILE *fp = fopen("test.txt", "r");
    if (fp == NULL) {
        // 方法1:使用 perror
        perror("fopen failed");
  
        // 方法2:使用 strerror + errno
        fprintf(stderr, "Error code: %d, Message: %s\n", 
                errno, strerror(errno));
        return EXIT_FAILURE;
    }

    printf("File opened successfully!\n");

    // ... 进行文件读写操作 ...

    if (fclose(fp) != 0) {
        perror("fclose failed");
        return EXIT_FAILURE;
    }

    printf("File closed successfully.\n");
    return EXIT_SUCCESS;
}
标准IO:缓存机制 2026-02-26
标准IO:字符、行 读写 2026-02-27

评论区