c++如何实现文件的安全覆盖更新_使用原子重命名操作【详解】

风萱君_2184

风萱君_2184

2026-04-08

576人浏览

原创

直接写文件不安全是因为非原子更新导致崩溃时文件损坏;原子重命名通过rename()保证“全成功或全失败”,但需同文件系统,且须fsync()确保落盘,再配合唯一临时名和校验可应对并发。

c++如何实现文件的安全覆盖更新_使用原子重命名操作【详解】

为什么直接写文件不安全

直接用 fopen 打开原文件并 fwrite 覆盖,一旦写入中途崩溃、断电或磁盘满,原文件就彻底损坏——既不是旧内容,也不是新内容。这是典型的“非原子更新”问题。

根本原因在于:普通写操作不提供“要么全成功、要么全回滚”的保障。操作系统和文件系统本身不保证单个 write() 调用的原子性(尤其对大文件),更不保证跨多个系统调用(open → write → close)的事务性。

原子重命名为什么能解决问题

POSIX 系统(Linux/macOS)和 Windows(从 Vista 起 via MoveFileEx with MOVEFILE_REPLACE_EXISTING)都保证:同一文件系统内,rename()(或其等价 API)是原子的。这意味着:rename("temp.dat", "config.json") 要么完全成功(旧文件瞬间被替换),要么完全失败(原文件毫发无损),绝不会出现中间态。

关键约束:rename() 的源和目标必须在同一个挂载点(即同个文件系统)。跨分区重命名会失败(EXDEV 错误),此时需 fallback 到拷贝 + 删除,但那就失去原子性了。

实操建议:

  • 先用 std::filesystem::temp_directory_path() 获取临时目录(确保与目标同分区,优先选同目录下 "xxx.tmp")
  • 写入时打开临时路径(如 "config.json.tmp"),完成 fsync() 或 fflush() + fsync() 后再 rename()
  • Windows 上用 std::filesystem::rename() 即可(C++17),它内部已处理 MoveFileEx 语义
  • Linux/macOS 下注意:若目标文件已存在且是只读,rename() 仍会成功(权限不影响原子性);但 Windows 会失败,需提前 chmod 或 SetFileAttributes

完整 C++17 实现示例(含错误防护)

以下代码封装了安全更新逻辑,覆盖常见边界:

C++14
C++14

C++14 对 C++11 的修正与增强版本,适合旧系统维护和较老工具链兼容。

下载
bool safe_overwrite(const std::filesystem::path& target, const std::string& content) {
    auto temp = target.parent_path() / (target.filename().string() + ".tmp");
    
    // 1. 写临时文件(O_CREAT|O_WRONLY|O_TRUNC)
    std::ofstream f(temp, std::ios::binary);
    if (!f.is_open()) return false;
    f = 0) {
        fsync(fd);
        close(fd);
    }

    // 3. 原子重命名(同文件系统前提下)
    try {
        std::filesystem::rename(temp, target);
        return true;
    } catch (const std::filesystem::filesystem_error&) {
        std::filesystem::remove(temp); // 清理残留
        return false;
    }
}

注意点:

  • 没调 fsync() 就 rename(),相当于把缓存里的脏页“假装”已落盘——断电后仍是空文件或截断文件
  • 不要用 std::ofstream::flush() 代替 fsync():它只刷 C++ 缓冲区,不保证内核页缓存写入磁盘
  • Windows 上若 target 正被其他进程以 SHARE_READ 打开,rename() 仍会成功;但若被独占打开(如记事本未关闭),则失败

多线程/多进程并发更新怎么办

rename() 本身是原子的,但多个进程同时尝试更新同一文件,仍可能因竞态导致某次写入丢失。例如:A 和 B 都生成自己的 .tmp 文件,然后都试图 rename("x.tmp", "x") —— 后执行者会覆盖先执行者的成果。

解决思路不是加锁(文件锁不可靠),而是让每次更新携带唯一标识:

  • 临时文件名加入 PID + 时间戳 + 随机数,如 config.json.tmp.12345.1712345678.abcde
  • 写入前先检查目标文件的 mtime 或校验和,确认是否已是期望版本(乐观并发控制)
  • 真正需要强一致性时,应改用数据库或带事务的日志结构存储,而非纯文件

文件系统的原子重命名只是基础保障,它解决的是“单次更新不损坏”,不解决“多次更新不冲突”。这点容易被忽略,尤其在配置热更新场景里。

C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!

相关文章

c++速学教程(入门到精通)
c++速学教程(入门到精通)

c++怎么学习?c++怎么入门?c++在哪学?c++怎么学才快?不用担心,这里为大家提供了c++速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

c++

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
json数据格式
json数据格式

JSON是一种轻量级的数据交换格式。本专题为大家带来json数据格式相关文章,帮助大家解决问题。

2023.08.07

2275

5

json是什么
json是什么

JSON是一种轻量级的数据交换格式,具有简洁、易读、跨平台和语言的特点,JSON数据是通过键值对的方式进行组织,其中键是字符串,值可以是字符串、数值、布尔值、数组、对象或者null,在Web开发、数据交换和配置文件等方面得到广泛应用。本专题为大家提供json相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.23

3702

1

jquery怎么操作json
jquery怎么操作json

操作的方法有:1、“$.parseJSON(jsonString)”2、“$.getJSON(url, data, success)”;3、“$.each(obj, callback)”;4、“$.ajax()”。更多jquery怎么操作json的详细内容,可以访问本专题下面的文章。

2023.10.13

1236

3

go语言处理json数据方法
go语言处理json数据方法

本专题整合了go语言中处理json数据方法,阅读专题下面的文章了解更多详细内容。

2025.09.10

4279

7

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

2023.08.10

4698

6

Python 多线程与异步编程实战
Python 多线程与异步编程实战

本专题系统讲解 Python 多线程与异步编程的核心概念与实战技巧,包括 threading 模块基础、线程同步机制、GIL 原理、asyncio 异步任务管理、协程与事件循环、任务调度与异常处理。通过实战示例,帮助学习者掌握 如何构建高性能、多任务并发的 Python 应用。

2025.12.24

1309

20

java多线程相关教程合集
java多线程相关教程合集

本专题整合了java多线程相关教程,阅读专题下面的文章了解更多详细内容。

2026.01.21

443

30

C++多线程相关合集
C++多线程相关合集

本专题整合了C++多线程相关教程,阅读专题下面的的文章了解更多详细内容。

2026.01.21

579

24

C# 多线程与异步编程
C# 多线程与异步编程

本专题深入讲解 C# 中多线程与异步编程的核心概念与实战技巧,包括线程池管理、Task 类的使用、async/await 异步编程模式、并发控制与线程同步、死锁与竞态条件的解决方案。通过实际项目,帮助开发者掌握 如何在 C# 中构建高并发、低延迟的异步系统,提升应用性能和响应速度。

2026.02.06

429

20

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
C++ Core Guidelines
C++ Core Guidelines

共0课时 | 0人学习

C++ Reference
C++ Reference

共0课时 | 0人学习

C++ 官方标准说明
C++ 官方标准说明

共0课时 | 0人学习