覆盖保存文件的非常安全的方法
Adequetely safe method of overwriting a save file?
使用cstdio
,覆盖文件的最安全方法是什么?'"安全"在这种情况下意味着文件不可能变得不完整或损坏;该文件将被完全覆盖,或者如果出现问题,它将是旧文件。
我想最好的方法是创建一个临时中间文件,然后在该中间文件完成后覆盖旧文件。如果这真的是最好的方法,那么还有一些其他问题似乎是可能的,尽管很罕见。
- 如果程序在重写时退出,我怎么知道该使用其他文件
- 如果程序在创建过程中退出,我怎么知道不使用其他文件
- 我怎么知道原始文件或中间体处于未定义状态(因为它可能在某种程度上失败它仍然可读,但它包含的数据是微妙的错误)
我想有一个很好的练习,但我没能找到。这是为保存的游戏数据;只有一个文件,而且每次都会覆盖整个文件,不需要担心部分覆盖或追加。
正如其他人所说,保留现有文件,并写入新文件。如果这是非常重要的(即,用户不可能恢复信息),请确保存在";备份";文件(例如,如果您的程序保存了abc.config
,则保留abc.old.config
或abc.backup
[如果您希望确保名称在任何地方都有效,则.cfg
和.bak
可能是更好的选择])。
编写文件时,请在文件中放置某种结束标记,这样可以确保文件是完整的。如果你想避免";用户编辑";对于该文件,您可能还需要内容的校验和(sha1、md5或类似)。如果结束标记不存在,或者校验和错误,那么您就知道该文件是";坏";,所以不要用它来备份。
- 将新内容写入临时文件(例如
fstream fout("abc.tmp");
) - 删除备份文件(如果存在)(例如
remove("abc.bak");
) - 将现在旧的文件重命名为备份名称(例如
rename("abc.cfg", "abc.bak");
) - 将新文件重命名为旧文件(例如
rename("abc.tmp", "abc.cfg"
)
对于所有步骤(尤其是写入实际数据),请检查是否存在错误。您需要决定哪里可以出现错误,哪里不可以(例如,不存在的文件的remove
可以,但如果rename
不起作用,您可能应该停止,否则可能会出现不好的结果)。
加载文件时,请检查所有步骤,如果出现错误,请返回备份文件。
您应该使用一个数据库管理系统来保证ACID。如果您坚持使用平面文件,则应该写入临时文件,在写入完成时复制并替换实际文件,并且只有在复制成功时才删除临时文件。此外,在每次写入文件时调用flush()
。
这是一个简单而有限的答案,它的实现提供了更多的清晰度。
它与Mats Petersson的答案相似,但只使用了两个文件,因为rename()
损坏文件的可能性似乎低于随着代码变得越来越复杂,我实现了一些错误。(应该是更改inode或主文件表条目与否触摸文件内容。如果您的应用程序可能部署在FAT文件系统上。)
步骤大致相同:
- 将临时文件写入与目标文件相同的目录中。如果此步骤失败,则中止
- 删除目标文件
- 将临时文件重命名为目标文件
下面是一个将char*写入文件的实现。我希望你有一个围绕文件函数的unicode包装器,所以一定要使用它!
// Replace unicode:: with your unicode-aware stdio functions.
#include <stdio.h>
static const char* k_TempExtension = ".tmp";
bool Storage::SaveFile_Safe(std::string& fullpath, const char* data, unsigned int len) {
std::string tempfile = fullpath + k_TempExtension;
bool success = false;
FILE* f = unicode::fopen(tempfile.c_str(), "wb");
if (f) {
const size_t count = 1;
size_t written = fwrite(data, len, count, f);
if (len == 0 && written == 0) {
// Close enough to writing everything.
written = count;
}
if (fclose(f) != 0) {
written = 0;
}
if (written == count) {
const char* tempfile_c = tempfile.c_str();
const char* fullpath_c = fullpath.c_str();
unicode::remove(fullpath_c);
// If we fail between these two function calls, we still have our
// temp file that we should attempt to load.
success = 0 == unicode::rename(tempfile_c, fullpath_c);
}
}
return success;
}
const char* Storage::LoadFile_Safe(std::string& fullpath) {
FILE* f = unicode::fopen(fullpath.c_str(), "rb");
if (f == 0) {
// If we failed during save, load the temp file.
std::string tempfile = fullpath + k_TempExtension;
f = unicode::fopen(tempfile.c_str(), "rb");
}
if (f == 0) {
return false;
}
// ... do loading code here
}
在将数据保存到文件中时,应防止关闭应用程序。你应该做的是加载旧文件,将其保存在一个变量中——覆盖变量的数据(在你的应用程序中),并将其写在旧文件上。所有操作都需要不到1秒的时间,因此您不必担心在保存时关闭应用程序。此外,只有在检查操作是否可行以及数据的完整性是否正确后,才能覆盖。
- 跨 DLL 边界访问虚拟方法是否安全/可能?
- 将传入的网络"char*"数据转换为"uint8_t"并返回的安全方法是什么?
- 使用 std::vector::swap 方法在C++中交换两个不同的向量是否安全?
- 在 c++ 中从执行的 shell 命令获取返回状态的安全方法是什么?
- 并发安全堆栈接口方法:正确与否?
- 将积分类型的数组作为另一个不相关的积分类型的阵列进行访问的安全且符合标准的方法
- 有没有一种简单的方法来检查C++中的不安全表达式
- 用非零值初始化void指针的正确(或最安全)方法
- 使用范围解析运算符时,在构造函数中调用虚拟方法是否安全?
- 是否有一种安全的方法可以断言字符串视图是否以 null 终止?
- 在C++线程内实现多个计时器的最安全方法
- 解决方法:QPixmap:在GUI线程之外使用pixmap是不安全的
- 最有效的安全方法将 std::map<int, std::shared_ptr> 转换为 std::<Base>map<int, std::shared_ptr<D
- 在移出向量上调用 size() 方法是否安全?
- 重新分配指针阵列的一部分的安全方法
- 最快和/或最安全的方法,在C 中加入两个宽字符串
- 一种安全、符合标准的方法,使类模板专用化仅在实例化时才无法使用"static_assert"进行编译
- 这种线程间通信方法安全吗?
- 这种加密方法安全吗
- 构造一个临时对象并调用一个返回指针的方法——安全吗?