必须主动检查流状态并验证读写字节数,禁用裸write()处理非pod类型、指针或跨平台数据,固定宽度类型+网络字节序+长度前缀是安全序列化的基础。

用 read() 和 write() 读写二进制数据,必须先确认流状态
这两个函数不抛异常,失败时静默返回,failbit 或 badbit 被置位但你根本不知道——除非主动检查。read() 返回 *this(即流对象),write() 同理,所以不能靠返回值判断成败。
常见错误现象:read() 后变量内容没变、或只读了部分字节,但程序继续往下跑,后续逻辑全错。
- 每次调用后立刻检查
ifs.good()或!ifs.fail(),别省这行 - 用
ifs.gcount()确认实际读取字节数,尤其处理不完整包或网络流残留时 - 打开文件务必加
std::ios::binary,否则 Windows 下\r\n会被悄悄转换,read()字节数对不上
自定义结构体直接 write() 前,得过三关:内存布局、填充、生命周期
把 struct 地址传给 write() 看似方便,但 C++ 不保证结构体是“纯数据块”。一旦含虚函数、非 POD 成员、引用或 std::string,直接 reinterpret_cast<char>(&obj)</char> 就是未定义行为。
使用场景:仅限简单聚合类型(plain old data),比如坐标点、帧头、协议固定字段。
- 用
static_assert(std::is_trivially_copyable_v<mystruct>)</mystruct>在编译期卡住非法类型 - 加
#pragma pack(1)或[[gnu::packed]]防止编译器填充,否则sizeof(MyStruct)≠ 字段字节和 - 成员里有指针?别写了——它存的是地址,另一端读出来毫无意义
read() 读不到预期字节数?优先查 eofbit 和缓冲区大小
不是所有“读少”都是文件损坏。更常见的是:文件提前结束、缓冲区不够、或流被意外关闭。例如读一个 int32_t(4 字节),但文件只剩 2 字节,read() 会读完这 2 字节并置 eofbit,gcount() 返回 2。
性能影响:反复小块读写比一次大块慢得多,尤其磁盘 I/O;但盲目加大缓冲又可能吃光栈(比如局部数组 1MB)。
- 永远用
gcount()检查真实读取量,别假设等于请求量 - 读结构体前先用
read()读长度字段,再按需分配缓冲,避免栈溢出 - 若需稳定读满,循环调用
read()+ 检查gcount(),直到累计够数或eofbit+gcount() == 0
跨平台序列化别碰裸 write(),至少处理字节序和类型尺寸
在 x86 上 write() 一个 int,到 ARM 设备上 read() 出来可能是负数或乱码——因为大小端不一致;更别说 int 在不同平台可能是 4 字节或 8 字节。
兼容性影响:同一份二进制文件,在 macOS 编译的程序可能读不出 Linux 写的数据。
- 基本类型一律用固定宽度类型:
int32_t、uint64_t,别用int或long - 写入前用
htons()/htonl()或bswap_32()统一转为大端(网络序),读取时再反转 - 字符串别直接
write(&str[0], str.size()),要先写长度字段(uint32_t),再写内容,否则无法安全还原
最麻烦的从来不是怎么写进去,而是多年后换语言、换架构、换人维护时,那份二进制文件到底按什么规则编码的——文档没写,注释删了,只有几个 reinterpret_cast 散落在角落。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











