如何同时读取和写入二进制文件?

问题描述 投票:0回答:1

如何同时读取和写入二进制文件?我似乎无法弄清楚为什么我的程序出错,并且我能想到的每一个调试都没有给我带来任何好的结果。该程序采用

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;
}
c++ logging memory binary fstream
1个回答
0
投票

所示代码中存在多个基本问题。第一个已经导致未定义的行为,这几乎使后续其余代码的保证失效:

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()

总而言之,必须解决多个问题:

  1. 删除并替换所有伪装成 C++ 的 C 代码。再见

    memset()
    。通过适当的序列化和反序列化重新实现基于
    sizeof()
    read()
    write()
    。稍微仔细看看所示的代码表明,使用
    >>
    的简单格式化输出操作应该足够了。它已被用于记录到
    std::cout
    。可以使用相同的代码来格式化每个日志条目,并且格式化输入提取运算符
    <<
    将用于读取现有日志条目,以进行比较。

  2. 修复记录更新错误导致错误记录的错误。跟踪日志文件中的文件位置,并在替换日志文件中的现有记录之前查找到正确的位置。

  3. 通过基于循环的

    eof()
    检查修复常见的逻辑缺陷。

© www.soinside.com 2019 - 2024. All rights reserved.