You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++二进制文件同时读写异常求助:程序崩溃与数据读取错误

C++二进制文件读写问题排查

问题概述

程序需求:接收string类型文件名,生成包含文件名和最后修改时间的文件信息,写入二进制日志文件;若日志已存在,则对比现有条目,与当前文件信息不同则更新对应日志条目。

错误现象

  1. 首次运行功能正常,二次运行时程序意外退出,错误码:
(process 14128) exited with code -1073741819.
  1. 更新待记录文件后,程序无错误退出,但控制台重复输出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;
}

问题根源分析

  1. 直接序列化std::string导致内存访问错误
    std::string是动态容器,内部存储的是指向堆内存的指针、字符串长度和容量,而非字符串内容本身。直接用write写入sizeof(std::pair<std::string, ...>),本质是把指针值写入文件。二次运行读取时,指针指向的内存已经无效,触发内存访问冲突(错误码-1073741819对应Windows下的STATUS_ACCESS_VIOLATION),同时读取后的std::string处于未定义状态,出现空字符串和初始时间戳。

  2. memset破坏std::string对象结构
    std::string是带构造函数的C++对象,用memset直接清零会破坏其内部成员变量(如指针、长度),导致未定义行为,可能引发崩溃或数据异常。

  3. 循环逻辑错误
    使用while (!log.eof())作为循环条件是错误的:eof()仅在读取操作失败并到达文件末尾时才会置位。最后一次读取失败后,仍会进入循环处理无效的log_entry,导致重复输出无效内容。

  4. 文件打开模式错误(精简版)
    精简版中首次创建文件使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 15:14:50