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

从std::ifstream读取二进制数据时避免未定义行为(UB)

二进制反序列化代码的未定义行为分析

简化假设

  • 文件格式完全正确,包含一个std::uint32_t、一个std::int64_t和一个float,字节序与程序匹配,且float的内存表示和程序一致
  • 仅处理隐式生存期、平凡可复制、平凡析构的类型

原始待审查代码

struct Content
{
    std::uint32_t first;
    std::int64_t second;
    float last;
};

std::string Path("<Some valid path>");    // 存储目标数据的二进制文件路径
std::ifstream is(Path, std::ios::binary);
Content content;
is.read(reinterpret_cast<char*>(&content.first), sizeof(content.first));
is.read(reinterpret_cast<char*>(&content.second), sizeof(content.second));
is.read(reinterpret_cast<char*>(&content.last), sizeof(content.last));

核心问题

这段代码里的reinterpret_cast是否合法?是否应该改用以下两种实现?

方案1:memcpy实现

char buffer[sizeof(std::uint32_t)];
is.read(buffer, sizeof(content.first));
std::memcpy(&content.first, buffer, sizeof(content.first));
// 对second、last重复上述逻辑

方案2:bit_cast实现

char buffer[sizeof(std::uint32_t)];
is.read(buffer, sizeof(content.first));
content.first = std::bit_cast<std::uint32_t>(buffer);
// 对second、last重复上述逻辑

问题解答

原始代码的合法性

在你给出的假设前提下,这段代码不存在未定义行为,是合法的。
原因是C++标准允许:将任何对象的存储区以字节序列的形式访问(即直接读取/写入对象的"对象表示"),而你处理的都是平凡可复制类型——这类类型的对象表示直接对应其值表示,且隐式生存期类型不需要显式构造即可直接操作其存储区。reinterpret_cast<char*>(&member)本质是把成员对象的指针转换成字节指针,用于读取字节到对象的存储区,完全符合标准规则。

替代方案的对比

  1. memcpy方案:这是标准明确认可的最安全的序列化/反序列化方式之一,完全没有UB风险。memcpy的语义就是复制字节序列,对于平凡可复制类型,用它将字节写入对象存储区是标准允许的初始化/赋值操作,兼容性极佳(支持所有C++版本)。
  2. bit_cast方案:C20引入的标准工具,要求源和目标类型的大小完全相同,且均为平凡可复制类型。它会直接将源的字节表示转换为目标类型的值,编译期就能检查大小匹配问题,类型安全性更高,代码也更简洁,是现代C中推荐的做法。

总结

原始代码在你的假设下是合法的,但memcpy和bit_cast是更稳妥、更符合现代C++最佳实践的选择,尤其是bit_cast能在编译期排查错误,优先推荐使用。


内容的提问来源于stack exchange,提问作者Oersted

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 18:33:23