写完文件后程序退出磁盘仍是旧内容,因C++/C默认缓冲数据先存内存,需fflush()清stdio缓冲、fsync()(Linux/macOS)或_commit()(Windows)强制落盘,并检查返回值确保成功。

为什么写完文件后程序退出,磁盘上还是旧内容
因为 C++ 标准库的 std::ofstream 和 C 的 FILE* 默认都带缓冲,数据先写进内存缓冲区,不是立刻落盘。系统或磁盘控制器还可能二次缓存(write-back cache),导致即使调用 fflush() 或 stream.flush(),文件在磁盘上仍没更新。
典型现象:程序写完立即退出,另一进程读到空/截断/旧数据;断电后文件损坏;日志最后一行丢失。
-
fflush()只清 C stdio 缓冲,不影响内核页缓存 -
fsync()(POSIX)或_commit()(Windows CRT)才真正把内核缓冲刷到物理设备 - Linux/macOS 用
fsync(),Windows 下必须用_commit()——fsync()在 MinGW 或 MSVC 中不保证有效,甚至编译失败
如何在 Windows 上用 _commit() 强制落盘
_commit() 是 Microsoft CRT 提供的等效接口,作用和 fsync() 一致:确保文件所有已写数据和元数据(如 mtime、size)持久化到磁盘。它只接受 int fd,所以得从流对象拿到底层文件描述符。
- 对
FILE*:用_fileno(fp)获取 fd,再传给_commit(fd) - 对
std::ofstream:需先用rdbuf()->pubsync()(仅清 stream 缓冲),再通过_fileno(_get_osfhandle(_fileno(fp)))绕不过去——更可靠的是用fopen()配合_commit(),避免 iostream 抽象层干扰 - 调用后务必检查返回值:
_commit()失败返回 -1,errno可能是EBADF(fd 无效)或EIO(磁盘错误)
示例(安全写入日志):
FILE* fp = fopen("log.txt", "a");
if (fp) {
fputs("done\n", fp);
fflush(fp); // 清 stdio 缓冲
if (_commit(_fileno(fp)) != 0) {
// handle error: disk full, permission denied, etc.
}
fclose(fp);
}
Linux/macOS 下 fsync() 的正确姿势与陷阱
fsync() 看似简单,但几个关键点常被忽略:它操作的是文件描述符,不是文件路径;它不保证目录项更新;它不能替代 close()。
- 必须在
write()或fwrite()后、close()前调用,否则 fd 可能已失效 - 如果文件是新建的(
open(..., O_CREAT | O_WRONLY)),还需对**父目录**调用fsync(),否则rename()或重启后可能找不到该文件(目录项还在 page cache) -
fdatasync()比fsync()轻量(不刷 mtime/ctime 等元数据),但某些场景(如数据库 WAL)仍需完整fsync() - 不要在循环里高频调用——每次
fsync()触发磁盘寻道,性能暴跌;批量写+单次fsync()更合理
示例(安全覆盖写):
int fd = open("data.bin", O_WRONLY | O_CREAT, 0644);
if (fd >= 0) {
write(fd, buf, len);
fsync(fd); // ← 关键:这里落盘
close(fd); // ← close 不等于落盘!
}
跨平台封装时最易错的三件事
想写一次代码跑两边?这几个点一踩一个准。
- 别直接宏定义
fsync → _commit:Windows 的_commit()不接受FILE*,且_fileno()对stdin/stdout等特殊流行为未定义 - 别忘了
fflush()/stream.flush():_commit()和fsync()都不管用户态缓冲,漏掉这步等于白刷 - 别假设
O_SYNC或ios_base::sync_with_stdio(false)能替代显式刷盘:前者降低写性能但不保证原子性;后者只是关闭 iostream 与 stdio 的同步,跟落盘无关
真正跨平台的最小安全单元是:写入 → 刷 stdio 缓冲 → 刷内核缓冲 → 检查错误。每一步缺一不可,少一个就可能丢数据。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











