用 fread 配合指针读取二进制文件需先分配内存(malloc/vector),以 "rb" 模式打开,正确设置元素大小与个数,检查返回值及错误状态;读结构体时须确保 POD、无 padding、字节序一致,优先用 memcpy 避免未对齐访问。

用 fread 配合指针读取二进制文件最直接
直接用 fread 把数据按字节块塞进内存,比自己循环读单字节快得多,也比 std::ifstream 的 read() 更贴近底层控制。关键不是“用指针”,而是让指针指向一块已分配的内存,再把 fread 的 ptr 参数设为它。
常见错误是传入未初始化或未分配的指针(比如 char* buf; fread(buf, ...)),结果段错误。必须先 malloc 或 new 一块足够大的缓冲区,或者用栈数组(但注意大小限制)。
-
fread第二个参数是单个元素大小,第三个是元素个数,别和总字节数混淆 —— 比如读 1024 字节,写成fread(buf, 1, 1024, fp),不是fread(buf, 1024, 1, fp) - 返回值是实际读取的元素个数,不是字节数;若小于预期,要检查
ferror(fp)或feof(fp),不能只看返回值是否为 0 - 文件必须以
"rb"模式打开,Windows 下缺了b可能因换行符转换导致读错字节
reinterpret_cast 用于结构体/POD 类型读取
如果二进制文件存的是结构体(比如网络包头、图像头),可以用指针强制类型转换,把读到的原始字节 reinterpret 成目标类型。前提是该类型是 POD(plain old data),且内存布局与文件一致(无 padding 差异、字节序匹配)。
例如读一个 4 字节整数头:
立即学习“C++免费学习笔记(深入)”;
int32_t header; fread(&header, sizeof(header), 1, fp); // 注意:这里 &header 是 int32_t*,但文件里存的可能是大端,而 x86 是小端 // 所以更安全的做法是读成 uint8_t[4],再手动拼接
- 不要对非 POD 类型(含虚函数、std::string 等)做
reinterpret_cast,行为未定义 - 结构体读取前建议用
#pragma pack(1)或alignas(1)消除 padding,否则sizeof(MyStruct)≠ 文件中实际占用字节数 - 跨平台时务必校验字节序,
ntohl()/ntohs()是标准做法,别依赖本地int32_t直接解包
用 std::vector<uint8_t></uint8_t> 管理内存更安全
裸指针容易忘 free 或 delete[],尤其出错分支多的时候。用 std::vector<uint8_t></uint8_t> 当缓冲区,既自动管理内存,又提供连续内存(&vec[0] 或 vec.data() 可当 void* 传给 fread)。
示例:
std::vector<uint8_t> buf(4096); size_t n = fread(buf.data(), 1, buf.size(), fp); buf.resize(n); // 裁剪到实际读取长度
-
resize()后buf.data()仍有效,但别在 resize 前保存旧指针 - 如果文件很大,别一次性分配全部内存 —— 先
fseek(fp, 0, SEEK_END); long size = ftell(fp);获取长度,再分配,避免std::bad_alloc -
std::vector的data()在 C++11 起保证连续,但早期编译器(如 VS2010)需确认,否则退回到&vec[0]
读取后指针别越界访问
读完只是把字节复制进内存,不代表你能随便用指针当任意类型解引用。比如读了 8 字节到 char buf[8],然后写 *(int64_t*)buf —— 这在 x86 上可能工作,但在 ARM 或某些编译器优化下会触发未对齐访问(bus error)或 UB。
- 用
memcpy拷贝到对齐变量里再访问,是最兼容的做法:int64_t val; memcpy(&val, buf, sizeof(val));
- 启用编译器警告(
-Wcast-alignon GCC/Clang)能提前发现这类危险转换 - 调试时用
gdb查看print/x $rax等寄存器值,比盲目解引用更可靠
真正麻烦的从来不是怎么读进来,而是读进来之后——字节序、对齐、padding、生命周期,每个都可能让程序在某个环境突然崩掉。


















