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()之后,无论后续操作成功还是失败,最终都必须调用fclose()。这是资源管理的铁律。
2.6 避坑指南
最后,分享几个我亲身踩过的坑:
- 忘记关闭文件:在嵌入式系统中,文件描述符是有限的。我曾经在一个循环里反复fopen()但忘记fclose(),结果跑了半天,系统报"Too many open files"。
- 模式选错导致数据丢失:用"w"模式打开配置文件,结果配置被清空。从那以后,我写文件前都会确认一下模式。
- 没有检查fclose()返回值:虽然少见,但磁盘写满时fclose()会失败,数据可能没完全落盘。
总结一句话:fopen()和fclose()是成对出现的。打开时检查返回值,关闭后置空指针。养成这个习惯,能省去你很多调试时间。