直接用fread读结构体数组可能出错,因结构体含padding、指针、非pod成员或跨平台对齐不一致;安全前提为仅对pod类型二进制读取,且写入与读取端结构体定义、编译器、abi完全一致,并须检查fread返回值是否等于预期元素个数。

直接用 fread 读结构体数组可能出错
结构体在内存中不是“纯数据块”,fread 会原样拷贝字节,但结构体可能含 padding、指针、非 POD 成员,或跨平台对齐不一致。一旦结构体里有 std::string、std::vector、虚函数表,或用了 #pragma pack 以外的对齐控制,fread(&arr[0], sizeof(T), n, fp) 就大概率读出垃圾值甚至崩溃。
安全前提:只对 plain old data(POD)类型做二进制读取,且文件写入时也用相同编译器、相同 ABI、相同结构体定义。
- 确认结构体是 POD:
std::is_pod_v<t></t>必须为true(C++17 起推荐用std::is_trivial_v<t> && std::is_standard_layout_v<t></t></t>) - 禁用非默认构造/析构/拷贝 —— 含任何成员函数或私有成员都可能破坏 POD 性质
- 确保写入和读取时
sizeof(T)完全一致(比如不同平台下long是 4 或 8 字节)
memcpy + fread 读取 POD 结构体数组的正确写法
即使满足 POD 条件,也不能直接把 fread 返回值当成功标志 —— 它返回实际读取的元素个数,可能少于预期(文件末尾截断、I/O 错误),必须检查。
struct Record {
int id;
double value;
char name[32];
};
static_assert(std::is_trivial_v<record> && std::is_standard_layout_v<record>);
<p>FILE<em> fp = fopen("data.bin", "rb");
if (!fp) { /</em> handle error */ }</p>
<p>size_t count = 1000;
std::vector<record> records(count);
size_t read_count = fread(records.data(), sizeof(Record), count, fp);
if (read_count != count) {
records.resize(read_count); // 实际只读了这么多
if (ferror(fp)) {
// 处理 I/O 错误,而非 EOF
}
}
fclose(fp);</record></p></record></record>
注意:fread 不保证字节序,若文件来自其他 CPU 架构(如 ARM 写、x86 读),需手动翻转整数字段。
遇到非 POD 结构体怎么办
只要结构体含 std::string、std::vector、引用、虚函数,就无法二进制直读。此时必须序列化/反序列化——即逐字段解析。
- 用
std::ifstream配合read()读原始字节,再手动填充字段(例如先读uint32_t len,再读len字节到std::string) - 字段顺序必须与写入时严格一致,包括字符串结尾是否存 '\0'、是否存长度前缀
- 避免依赖
sizeof(std::string)—— 它只是小对象的固定头大小,真实内容在堆上
示例片段(读一个带长度前缀的字符串):
uint32_t len; fin.read(reinterpret_cast<char>(&len), sizeof(len)); std::string s(len, '\0'); fin.read(&s[0], len); </char>
Windows 下 fopen 必须加 "b" 模式
在 Windows 上,文本模式会把 \r\n 自动转成 \n,并可能提前终止读取(遇到 0x1A 视为 EOF)。二进制文件若没加 "rb",fread 可能少读、错读,甚至中途返回。
- Linux/macOS 对
"b"忽略,但显式加上更可移植 -
fopen("data.bin", "r")→ 错误;fopen("data.bin", "rb")→ 正确 - 同理,写入时用
"wb",别用"w"
padding 和对齐这类细节,往往只有在换编译器、升级标准库、或结构体加新字段后才暴露 —— 别等上线才发现读出来的 id 全是 0。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











