正确姿势是先用 fseek+ftell 获取文件大小并检查溢出,malloc 分配缓冲区,fseek 回起始位置,再以 fread(buf, 1, size, fp) 读取,并严格校验返回值是否等于 size。
用
一次性读完二进制文件到内存数组的正确姿势
直接调用
读取整个文件到预分配数组是可行的,但必须先获取文件大小并确保缓冲区足够——否则轻则数据截断,重则越界写入。别信“读多少算多少”的侥幸心理。
先用
+
获取真实字节长度,注意
返回
,大文件需检查是否溢出(如 >2GB 时在某些平台可能出错)
分配缓冲区用
,别用栈数组(
),栈空间太小且不可控
调用前必须
回到开头,否则从 EOF 开始读,
直接返回 0
的第二个参数设为
最稳妥:它表示每个“元素”字节数,设成
才能保证返回值等于实际读取字节数;若设成
,返回值是“成功读取的元素个数”,容易误判
为什么
返回值不等于文件大小?常见掉坑点
返回值小于预期,不一定是磁盘错误,大概率是操作逻辑或类型匹配问题。
文件是以文本模式(
没写对,写成
)打开的:Windows 下会把
当作一个字符处理,导致
实际读取字节数变少
缓冲区指针为空或未初始化,
行为未定义,可能返回 0 也可能崩溃
读取后没检查返回值:
—— 这步跳过,后面所有基于“已全量读入”的假设都崩了
多线程环境下共享
且没加锁,
/
顺序可能被干扰
批量读取 vs 循环小块读取:什么场景该选哪种?
不是所有情况都适合“一口吞”。内存受限、流式处理或校验需求强时,分块更稳。
适合一次性读:配置文件、资源包、已知小而固定的二进制结构(如 PNG 头 8 字节、自定义协议头)
适合循环读:网络响应体、日志文件、不确定大小的传感器数据流——每次读
或
字节,边读边解析或写入磁盘,避免 malloc 大内存失败
注意:循环读时,
单次返回值可能小于请求长度(哪怕文件没结束),这是正常行为,不代表错误;只有返回 0 且
或
为真才需终止
读取后数据怎么用?别直接当字符串处理
二进制数据不含
终止符,用
或
会越界或截断——这是新手最常踩的隐性坑。
需要按结构体解析?确保结构体打包对齐一致:
或
,否则
可能因内存布局差异读错字段
要 hex dump 调试?用
避免符号扩展
后续要交给其他库(如 zlib、openssl)?确认它们是否要求内存对齐(如 16 字节),必要时用
替代
真正麻烦的从来不是
本身,而是你默认它“只是读”,却忘了它读出来的是裸字节——没有编码、没有边界、没有意图。每一步都要自己声明清楚:我要读多少、存在哪、怎么解释它。
freadfreadfseek(fp, 0, SEEK_END)ftell(fp)ftelllongmalloc(file_size)char buf[1024*1024]fseek(fp, 0, SEEK_SET)freadfread(buf, 1, file_size, fp)11sizeof(int)fread"rb""r"\r\nfreadfreadsize_t n = fread(buf, 1, size, fp); if (n != size) { /* 缺失处理 */ }FILE*fseekfreadfread409665536freadferror(fp)feof(fp)\0printf("%s", buf)strlen(buf)#pragma pack(1)__attribute__((packed))fread(&header, sizeof(header), 1, fp)for (int i = 0; i ,强制转 unsigned charposix_memalignmallocfread