C++二进制文件同时读写异常求助:程序崩溃与数据读取错误
问题概述
程序需求:接收string类型文件名,生成包含文件名和最后修改时间的文件信息,写入二进制日志文件;若日志已存在,则对比现有条目,与当前文件信息不同则更新对应日志条目。
错误现象
- 首次运行功能正常,二次运行时程序意外退出,错误码:
(process 14128) exited with code -1073741819.
- 更新待记录文件后,程序无错误退出,但控制台重复输出86次无效日志条目:
Log Entry: { , 1601-01-01 00:00:00.0000000 } is up to date.
相关代码
精简版测试函数
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; } }
完整checkLog函数
#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 }
main函数
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; }
问题根源分析
直接序列化std::string导致内存访问错误
std::string是动态容器,内部存储的是指向堆内存的指针、字符串长度和容量,而非字符串内容本身。直接用write写入sizeof(std::pair<std::string, ...>),本质是把指针值写入文件。二次运行读取时,指针指向的内存已经无效,触发内存访问冲突(错误码-1073741819对应Windows下的STATUS_ACCESS_VIOLATION),同时读取后的std::string处于未定义状态,出现空字符串和初始时间戳。memset破坏std::string对象结构
std::string是带构造函数的C++对象,用memset直接清零会破坏其内部成员变量(如指针、长度),导致未定义行为,可能引发崩溃或数据异常。循环逻辑错误
使用while (!log.eof())作为循环条件是错误的:eof()仅在读取操作失败并到达文件末尾时才会置位。最后一次读取失败后,仍会进入循环处理无效的log_entry,导致重复输出无效内容。文件打开模式错误(精简版)
精简版中首次创建文件使用std::fstream的out|in|binary模式,而std::fstream默认不会创建新文件,导致文件未被正确创建,后续写入和读取操作均失败,读取到空内容。
修复方案
1. 正确序列化/反序列化数据
放弃直接序列化std::pair,改为序列化字符串的长度和内容,再序列化时间戳:
- 写入逻辑:先写入
std::string的长度(size_t类型),再写入字符串的原始数据;接着写入file_time_type的二进制数据。 - 读取逻辑:先读取
size_t长度,再读取对应长度的字符构造std::string;接着读取file_time_type数据。
2. 移除memset对C++对象的操作
用默认构造或赋值操作初始化对象,避免破坏对象结构:
std::pair<std::string, std::filesystem::file_time_type> log_entry; // 替代memset(&log_entry, 0, sizeof(log_entry));
3. 修正循环条件
将读取操作放在循环条件中,确保只有读取成功时才进入循环:
std::pair<std::string, std::filesystem::file_time_type> log_entry; size_t str_len; while (log.read(reinterpret_cast<char*>(&str_len), sizeof(str_len))) { // 读取字符串内容 log_entry.first.resize(str_len); log.read(log_entry.first.data(), str_len); // 读取时间戳 log.read(reinterpret_cast<char*>(&log_entry.second), sizeof(log_entry.second)); // 后续处理逻辑... }
4. 修正文件打开模式
首次创建文件使用std::ofstream的out|binary模式(ofstream默认会创建新文件并截断已有内容):
std::ofstream log(SHADER_LOG, std::ios::binary);
修正后的示例代码
#define SHADER_LOG "bin\\shader_log.bin" void writeLogEntry(std::ostream& log, const std::pair<std::string, std::filesystem::file_time_type>& entry) { // 写入字符串长度 size_t str_len = entry.first.size(); log.write(reinterpret_cast<const char*>(&str_len), sizeof(str_len)); // 写入字符串内容 log.write(entry.first.data(), str_len); // 写入时间戳 log.write(reinterpret_cast<const char*>(&entry.second), sizeof(entry.second)); } bool readLogEntry(std::istream& log, std::pair<std::string, std::filesystem::file_time_type>& entry) { // 读取字符串长度 size_t str_len; if (!log.read(reinterpret_cast<char*>(&str_len), sizeof(str_len))) { return false; } // 读取字符串内容 entry.first.resize(str_len); if (!log.read(entry.first.data(), str_len)) { return false; } // 读取时间戳 if (!log.read(reinterpret_cast<char*>(&entry.second), sizeof(entry.second))) { return false; } return true; } 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"; if (!std::filesystem::exists(SHADER_LOG)) { std::ofstream log(SHADER_LOG, std::ios::binary); writeLogEntry(log, fileInfo); log.close(); std::cout << "Created new log entry.\n"; return; } std::fstream log(SHADER_LOG, std::ios::in | std::ios::out | std::ios::binary); if (!log.is_open()) { throw std::runtime_error(std::format("Failed to open {}!", SHADER_LOG)); } std::pair<std::string, std::filesystem::file_time_type> log_entry; bool entry_found = false; std::streampos entry_pos; // 遍历日志条目 while (readLogEntry(log, log_entry)) { if (log_entry.first == fileInfo.first) { entry_found = true; entry_pos = log.tellg() - (sizeof(fileInfo.second) + fileInfo.first.size() + sizeof(size_t)); if (log_entry.second != fileInfo.second) { // 移动到条目起始位置,更新内容 log.seekp(entry_pos); writeLogEntry(log, fileInfo); std::cout << "Log Entry: { " << log_entry.first << ", " << log_entry.second << " }\n"; std::cout << "Log successfully updated!\n"; } else { std::cout << "Log Entry: { " << log_entry.first << ", " << log_entry.second << " }\n"; std::cout << std::format("{} is up to date.\n", log_entry.first); } break; } } // 如果未找到条目,追加到文件末尾 if (!entry_found) { log.seekp(0, std::ios::end); writeLogEntry(log, fileInfo); std::cout << "Added new log entry for " << filename << ".\n"; } log.close(); std::cout << std::format("{} file closed successfully!\n", SHADER_LOG); }
内容的提问来源于stack exchange,提问作者ModernEraCaveman

