c++ - 严格别名和二进制 I/O

标签 c++ language-lawyer

让我们考虑以下用于读取二进制文件内容的(简化的)代码:

struct Header
{
    char signature[8];
    uint32_t version;
    uint32_t numberOfSomeChunks;
    uint32_t numberOfSomeOtherChunks;
};

void readFile(std::istream& stream)
{
    // find total size of the file, in bytes:
    stream.seekg(0, std::ios::end);
    const std::size_t totalSize = stream.tellg();

    // allocate enough memory and read entire file
    std::unique_ptr<std::byte[]> fileBuf = std::make_unique<std::byte[]>(totalSize);
    stream.seekg(0);
    stream.read(reinterpret_cast<char*>(fileBuf.get()), totalSize);

    // get the header and do something with it:
    const Header* hdr = reinterpret_cast<const Header*>(fileBuf.get());

    if(hdr->version != expectedVersion) // <- Potential UB?
    {
        // report the error
    }

    // and so on...
}

在我看来,下面一行:

if(hdr->version != expectedVersion) // <- Potential UB?

包含未定义的行为:我们正在读取 uint32_t 类型的 version 成员,它覆盖在 std::byte 数组之上对象,并且编译器可以自由地假设 uint32_t 对象不会别名任何其他内容。

问题是:我的解释正确吗?如果是,如何修复此代码?如果没有,为什么这里没有 UB?

注意 1: 我理解严格别名规则的目的(允许编译器避免不必要的内存加载)。此外,我知道在这种情况下使用 std::memcpy 将是一个安全的解决方案 - 但使用 std::memcpy 将意味着我们必须进行额外的内存分配(在堆栈上,如果对象的大小未知,则在堆上)。

最佳答案

The question is: is my interpretation correct?

是的。

If yes, what can be done to fix this code?

您已经知道 memcpy 是一种解决方案。但是,您可以通过直接读取 header 对象来跳过 memcpy 和额外的内存分配:

Header h;
stream.read(reinterpret_cast<char*>(&h), sizeof h);

请注意,以这种方式读取二进制文件意味着文件的整数表示必须与 CPU 的表示相匹配。这意味着该文件不可移植到具有不同 CPU 架构的系统。

关于c++ - 严格别名和二进制 I/O,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54337044/

相关文章:

c++ - 带 vector 的 OpenMP C++ 程序

c++ - C++ 成员函数隐藏的原因

c++ - 如何浏览 C++ 或 C 中的文件夹?

c++11 - 是全局变量 constexpr 的地址吗?

c++ - 伪析构函数名称对非类和非枚举类型有意义吗?

c++ - 我应该使用 "&&"还是 "and"?

c++ - 右值、左值和正式定义

c++ - 谷歌风格指南 "<chrono> is an unapproved C++11 header"

c++ - 调用 glTexSubImage2D 时出现 OpenGL 错误 'invalid value'

c++ - 静态初始化的非文字对象的销毁顺序