c++ - 严格的别名和二进制 I/O
问题描述
让我们考虑以下(简化的)代码来读取二进制文件的内容:
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
的类型的成员,并且编译器可以自由地假设该对象没有其他任何别名。uint32_t
std::byte
uint32_t
问题是:我的解释正确吗?如果是,可以做些什么来修复此代码?如果没有,为什么这里没有UB?
注1:我理解严格别名规则的目的(允许编译器避免不必要的内存负载)。另外,我知道在这种情况下 usingstd::memcpy
将是一个安全的解决方案 - 但 usingstd::memcpy
意味着我们必须进行额外的内存分配(在堆栈上,或者如果对象的大小未知,则在堆上)。
解决方案
问题是:我的解释正确吗?
是的。
如果是,可以做些什么来修复此代码?
您已经知道 memcpy 是一种解决方案。但是,您可以通过直接读取标头对象来跳过 memcpy 和额外的内存分配:
Header h;
stream.read(reinterpret_cast<char*>(&h), sizeof h);
请注意,以这种方式读取二进制文件意味着文件的整数表示必须与 CPU 的表示匹配。这意味着该文件不能移植到具有不同 CPU 架构的系统中。
推荐阅读
- r - 使用 Reactable Shiny 以很好的方式分离表格
- javascript - 使用exceljs writefile时如何给出路径?
- php - 从子域页面上传图片文件到主域
- sas - 需要根据个别阳性测试结果保留实验室类别测试
- c++ - --DDISPLAY 标志有什么作用,如何从 pkg-config 调用它?
- arrays - 将数组位置分配给标签标记属性
- oracle - 如何从远程机器运行 Expdp?
- azure - 在需要身份验证才能访问的方案中使用 Azure CDN 的最佳做法?
- civicrm - CiviCRM 问候语(电子邮件和邮政)中可用的所有可能变量字段是什么?
- python - Pandas 为新列分配值时出现问题