直接fopen(...,"w")覆盖不安全,因写入中途崩溃、断电或被杀会导致原文件清空而新内容未写完,数据彻底丢失;操作系统不保证write/fwrite原子性,尤其大文件或磁盘满时。

为什么直接 fopen(..., "w") 覆盖文件不安全?
因为写入中途崩溃、断电或程序被杀时,原文件内容可能已清空但新内容未写完,导致数据彻底丢失。操作系统也不保证 write() 或 fwrite() 调用的原子性,尤其在大文件或磁盘满等边界情况下。
安全更新的核心思路是:先写入一个临时文件(与目标同磁盘分区),再用原子操作替换原文件。Linux/macOS 依赖 rename() 的原子性;Windows 需用 MoveFileEx() 并指定 MOVEFILE_REPLACE_EXISTING 标志。
如何生成可靠临时文件路径(跨平台)
不能手拼 "temp.txt" 或用 tmpnam()(已被 C++23 标记为废弃,且线程不安全)。正确做法是基于目标路径构造临时名,并确保在同一文件系统上。
- Linux/macOS:用
std::filesystem::temp_directory_path()不够——它返回的是系统临时目录,可能和目标文件不在同一挂载点,rename()会失败 - 推荐方案:在目标文件所在目录下生成临时名,例如
original.txt→original.txt.part或original.txt.XXXXXX - 用
std::filesystem::path::replace_extension(".part")或手动拼接,再调用std::filesystem::create_temporary_file()(C++20)更稳妥,它会在指定目录创建唯一临时文件
写入临时文件后,怎样原子替换原文件?
关键不是“写完就删”,而是让替换操作不可中断、不可回滚。不同平台 API 行为差异大:
立即学习“C++免费学习笔记(深入)”;
- Linux/macOS:
std::filesystem::rename(temp_path, target_path)是原子的——只要两者同属一个文件系统 - Windows:
std::filesystem::rename()在 C++17 实现中可能退化为复制+删除,不保证原子性;必须用 Win32 API:MoveFileExA(temp_path.c_str(), target_path.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) - 务必检查返回值:替换失败时,临时文件应主动删除(避免残留),原文件保持不变
- 替换前可选调用
fsync()(POSIX)或FlushFileBuffers()(Windows)确保临时文件数据落盘,否则重命名后可能仍缓存在内存中
异常安全与清理逻辑怎么写才不漏?
最易出错的是“只管成功路径,不管失败后状态”。临时文件没删干净、原文件被意外截断、权限丢失,都会让下次更新失败。
- 用 RAII 封装临时文件生命周期:构造时创建,析构时若未提交则自动删除(
std::unique_ptr+ 自定义 deleter 可行) - 写入过程中抛异常?确保析构函数能捕获并清理;不要依赖
try/catch外围手动处理——容易遗漏分支 - 替换失败后,不要尝试“恢复原文件”(备份不可靠),而是保留原文件 + 清理临时文件 + 报错,由上层决定是否重试
- 注意权限继承:临时文件默认权限常为
0600,替换后需显式调用std::filesystem::permissions(temp_path, std::filesystem::perms::owner_read | std::filesystem::perms::owner_write)再 rename,否则新文件可能不可读



















