2. 文件的打开与关闭:fopen()与fclose()深度解析

大家好,我是你们的嵌入式讲师。今天我们来聊聊文件操作中最基础、也最容易踩坑的两个函数——fopen()fclose()

说实话,这两个函数看起来简单,但我在项目里见过太多人因为打开模式选错、或者忘记关闭文件,导致数据丢失、内存泄漏。嗯,咱们今天就把它们彻底讲透。

2.1 fopen():打开文件的"钥匙"

fopen()的原型长这样:

FILE *fopen(const char *filename, const char *mode);

它返回一个FILE *指针。如果打开失败,返回NULL。就这么简单?别急,坑都在后面。

核心要点:每次调用fopen()后,必须检查返回值是否为NULL。我见过太多新手直接拿着指针就用,结果程序崩溃得一塌糊涂。

我曾经在一个数据采集项目中,因为SD卡没插好,fopen()返回了NULL,但代码没做检查,结果后续的fwrite()直接写到了非法地址,整个系统死机。从那以后,我写任何文件操作代码,第一件事就是检查返回值。

2.2 文件打开模式:选错模式,数据全丢

打开模式是fopen()的灵魂。选错了,轻则读不到数据,重则把原有文件内容清空。咱们一个一个来看。

模式 含义 文件存在时 文件不存在时
"r" 只读 正常打开 返回NULL
"w" 只写 清空内容 创建新文件
"a" 追加 保留内容,从末尾写 创建新文件
"r+" 读写(不创建) 正常打开 返回NULL
"w+" 读写(创建/覆盖) 清空内容 创建新文件
"a+" 读写(追加) 保留内容,从末尾写 创建新文件

警告:使用"w""w+"模式时,如果文件已存在,内容会被立即清空。没有确认,没有回收站。我曾经有个同事,调试时不小心把配置文件用"w"模式打开了,结果几百个设备的配置参数全没了……

我个人习惯是:读文件用"r",写新文件用"w",追加日志用"a"。如果既要读又要写,优先考虑"r+",因为它不会破坏原有数据。

2.3 fclose():用完记得关门

fclose()的原型:

int fclose(FILE *stream);

成功返回0,失败返回EOF。你可能会想:不就是一个关闭操作吗,能有什么坑?

其实不然。我遇到过最典型的问题:程序异常退出时,fclose()没被调用,导致缓冲区里的数据没写进文件。你想想看,辛辛苦苦采集了一天的数据,结果因为没关文件,全丢了。

我的建议:在嵌入式系统中,尽量在fclose()之后将FILE指针置为NULL,防止野指针。像这样:

fclose(fp);
fp = NULL;

2.4 完整示例:一个日志记录器

下面是我写的一个简单日志记录函数,它展示了fopen()和fclose()的正确用法:

#include <stdio.h>
#include <time.h>

int write_log(const char *msg) {
    FILE *fp = fopen("system.log", "a");
    if (fp == NULL) {
        perror("打开日志文件失败");
        return -1;
    }

    time_t now = time(NULL);
    fprintf(fp, "[%s] %s\n", ctime(&now), msg);

    if (fclose(fp) != 0) {
        perror("关闭日志文件失败");
        return -1;
    }
    return 0;
}

注意几个细节:

  • 使用"a"模式追加,不会覆盖历史日志
  • 每次写完后立即关闭,防止数据丢失
  • 检查了fclose()的返回值——虽然很少失败,但万一磁盘满了呢?

2.5 知识体系总览

下面这张图,是我梳理的文件打开与关闭的核心逻辑:

文件打开与关闭核心流程 fopen() 成功? 返回NULL / 错误处理 文件操作 fclose() 无论成功或失败,最终都要关闭文件

这张图想表达的核心思想是:fopen()之后,无论后续操作成功还是失败,最终都必须调用fclose()。这是资源管理的铁律。

2.6 避坑指南

最后,分享几个我亲身踩过的坑:

  • 忘记关闭文件:在嵌入式系统中,文件描述符是有限的。我曾经在一个循环里反复fopen()但忘记fclose(),结果跑了半天,系统报"Too many open files"。
  • 模式选错导致数据丢失:用"w"模式打开配置文件,结果配置被清空。从那以后,我写文件前都会确认一下模式。
  • 没有检查fclose()返回值:虽然少见,但磁盘写满时fclose()会失败,数据可能没完全落盘。

总结一句话:fopen()和fclose()是成对出现的。打开时检查返回值,关闭后置空指针。养成这个习惯,能省去你很多调试时间。


公众号:蓝海资料掘金营,微信deep3321