让我们考虑以下(简化的)代码来读取二进制文件的内容:
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?
包含未定义的行为:我们正在读取类型为version
的{{1}}成员,该成员覆盖在uint32_t
对象数组的顶部,并且编译器可以自由地假设{{1 }}对象没有其他别名。
问题是:我的解释正确吗?如果是,该如何解决此代码?如果没有,为什么这里没有UB?
注释1:我了解严格的别名规则的目的(允许编译器避免不必要的内存负载)。另外,我知道在这种情况下,使用std::byte
是一种安全的解决方案-但是使用uint32_t
意味着我们必须做额外的内存分配(在堆栈上,或者在堆上,如果对象的大小是未知)。
答案 0 :(得分:3)
问题是:我的解释正确吗?
是的
如果是,该如何解决此代码?
您已经知道memcpy是一种解决方案。但是,您可以通过直接读取标头对象来跳过memcpy和额外的内存分配:
Header h;
stream.read(reinterpret_cast<char*>(&h), sizeof h);
请注意,以这种方式读取二进制文件意味着文件的整数表示形式必须与CPU的表示形式匹配。这意味着该文件无法移植到具有不同CPU体系结构的系统。
答案 1 :(得分:0)
该如何解决此代码?
等待http://wg21.link/P0593或类似的允许在char
/ unsigned char
/ std::byte
数组中隐式创建对象的事情。