如何同时读取和写入二进制文件?我似乎无法弄清楚为什么我的程序出错,并且我能想到的每一个调试都没有给我带来任何好的结果。该程序采用
string
并生成要写入二进制文件的文件信息。如果日志二进制文件存在,则程序会将文件中的信息与日志中的信息进行比较。如果信息不同,则程序更新日志。我做的功能如下:
#define SHADER_LOG "bin\\shader_log.bin"
void checkLog(const std::string& filename) {
std::string _path = std::filesystem::absolute(".\\shaders\\" + filename).string();
std::filesystem::file_time_type last_update = std::filesystem::last_write_time(_path);
std::pair<std::string, std::filesystem::file_time_type> fileInfo{filename, last_update};
std::cout << "File Info: { " << fileInfo.first << ", " << fileInfo.second << " }\n";
std::pair<std::string, std::filesystem::file_time_type> log_entry;
memset(&log_entry, 0, sizeof(log_entry));
if (!std::filesystem::exists(SHADER_LOG)) {
std::ofstream log(SHADER_LOG, std::ios_base::out | std::ios::binary);
log.write(reinterpret_cast<const char*>(&fileInfo), sizeof(fileInfo));
log.close();
return;
}
std::fstream log(SHADER_LOG, std::ios_base::in | std::ios_base::out | std::ios::binary);
if (!log.is_open()) {
throw std::runtime_error(std::format("Failed to open {}!", SHADER_LOG));
}
int i = 0;
while (!log.eof()) {
log.read(reinterpret_cast<char*>(&log_entry), sizeof(log_entry));
//log.getline(reinterpret_cast<char*>(&log_entry), sizeof(log_entry));
i++;
if ((log_entry.first == fileInfo.first) && (log_entry.second != fileInfo.second))
{// Update log entry
log.write(reinterpret_cast<const char*>(&fileInfo), sizeof(fileInfo));
std::cout << "Log Entry: { " << log_entry.first << ", " << log_entry.second << " }\n";
std::cout << "Log successfully updated!\n";
}
else
{// Print to manually confirm update status
std::cout << "Log Entry: { " << log_entry.first << ", " << log_entry.second << " }\n";
std::cout << std::format("{} is up to date.\n", log_entry.first);
std::cout << i << std::endl; // debugging number of times the loop is run
}
}
log.close();
if (!log.is_open()) {
std::cout << std::format("{} file closed successfully!\n", SHADER_LOG);
}
return; // trying to debug exiting the function
}
首次运行时,该功能似乎按预期工作。然而,在第二次运行时,程序意外退出并给出错误代码:
(process 14128) exited with code -1073741819.
更新要记录的文件后,该函数退出时没有上述代码,但打印到控制台的信息显示了有关该问题的更多信息:
Log Entry: { , 1601-01-01 00:00:00.0000000 }
is up to date.
在函数退出之前,上面的内容打印了大约 86 次,没有返回任何错误。
我尝试将函数压缩为下面的函数,以尝试揭示有关问题的更多信息:
void checkLog(const std::string& filename) {
std::string _path = std::filesystem::absolute(".\\shaders\\" + filename).string();
std::filesystem::file_time_type last_update = std::filesystem::last_write_time(_path);
std::pair<std::string, std::filesystem::file_time_type> fileInfo{filename, last_update};
std::cout << "File Info: { " << fileInfo.first << ", " << fileInfo.second << " }\n";
std::pair<std::string, std::filesystem::file_time_type> log_entry;
memset(&log_entry, 0, sizeof(log_entry));
if (!std::filesystem::exists(SHADER_LOG)) {
std::fstream log(SHADER_LOG, std::ios_base::out | std::ios_base::in | std::ios::binary);
log.write(reinterpret_cast<const char*>(&fileInfo), sizeof(fileInfo));
log.seekp(0); // tried .seekp and .seekg
/* Debugging */
std::pair<std::string, std::filesystem::file_time_type> log_entry {};
memset(&log_entry, 0, sizeof(log_entry)); // Read in another stack thread to clear memory to avoid alignment errors
log.getline(reinterpret_cast<char*>(&log_entry), sizeof(log_entry));
std::cout << "Log Entry: { " << log_entry.first << ", " << log_entry.second << " }\n";
log.close();
return;
}
}
这确实给了我更多关于可能导致问题的信息,但我仍然不知道解决方案。压缩版本的输出表明我的程序无法写入日志文件:
File Info: { shader.comp, 2023-08-15 00:43:48.7875194 }
Log Entry: { , 1601-01-01 00:00:00.0000000 }
is up to date.
请帮助我。我无法弄清楚我做错了什么,并且我开始认为不可能同时读取和写入二进制文件。
编辑:
int main() {
try {
checkLog("shader.comp");
std::cout << "Exited Compiler successfully!\n";
/* When it gives the error code, it does not print the above line to the console */
}
catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
所示代码中存在多个基本问题。第一个已经导致未定义的行为,这几乎使后续其余代码的保证失效:
memset(&log_entry, 0, sizeof(log_entry));
log_entry
是包含 std::pair
的 std::string
。
memset
只能用于 POD 对象。使用它,以这种暴力方式攻击 C++ 类的对象,例如 std::string
是未定义的行为。
这种
memset
用法在 C 代码中很常见,用于清除包含 struct
的未初始化内存。然而这是C++,而不是C,并且。幸运的是,在 C++ 中我们不需要诉诸这种原始初始化。这就是构造函数和析构函数的用途,在 C++ 中我们不这样做(除非我们真的想对 POD 对象这样做)。然而,从技术上讲,这现在是未定义的行为,并且从代码的其余部分观察到的结果是无效的。但还有一些其他明显的缺陷也需要解决。
while (!log.eof()) {
这是一个非常常见的错误,链接的答案对此进行了充分解释。
log.read(reinterpret_cast<char*>(&log_entry), sizeof(log_entry));
我们的朋友
log_entry
带着std::string
回来了。
sizeof()
是一个 编译时间常数。它是在编译时计算的。现在不可能那个std::string
是空的,还是包含哈利波特书籍的完整内容。这里的期望是将整个 log_entry
保存到文件中。显然,此处写入的数据量会有所不同,具体取决于 std::string
的大小。
所示的代码似乎假设每个日志记录的大小是固定的,但如果您停下来想一想,那么很明显,这个假设显然是有缺陷的。每条记录的大小各不相同。整体方法在功能上也存在缺陷。
if ((log_entry.first == fileInfo.first) && (log_entry.second != fileInfo.second))
{// Update log entry
log.write(reinterpret_cast<const char*>(&fileInfo), sizeof(fileInfo));
忽略
sizeof
问题以及只能以这种方式读取或写入 POD 对象的事实:上述代码的明显意图是用更新后的日志文件中的匹配条目替换内容。
不幸的是,日志文件中的现有条目已被读取。读/写位置现在是其后面的记录。相反,这将打破下一个记录。
然而,这仍然是一个有争议的问题,因为这个
read()
和 write()
本身就存在根本缺陷。这种 read()
/write()
模式也是典型的 C 代码,如 memset()
。
总而言之,必须解决多个问题:
删除并替换所有伪装成 C++ 的 C 代码。再见
memset()
。通过适当的序列化和反序列化重新实现基于 sizeof()
的 read()
和 write()
。稍微仔细看看所示的代码表明,使用 >>
的简单格式化输出操作应该足够了。它已被用于记录到 std::cout
。可以使用相同的代码来格式化每个日志条目,并且格式化输入提取运算符 <<
将用于读取现有日志条目,以进行比较。
修复记录更新错误导致错误记录的错误。跟踪日志文件中的文件位置,并在替换日志文件中的现有记录之前查找到正确的位置。
通过基于循环的
eof()
检查修复常见的逻辑缺陷。