必须先用stat/fstat或getfilesizeex检查文件大小再fread,否则易oom;分块读需累加gcount()校验总字节数;mmap仍可能触发oom;boost.iostreams需手动加limiting_filter并前置stat。

用 fread 读文件前必须检查文件大小
直接 fread 读整个文件进内存,是 C++ 里最常见 OOM 原因之一。系统不会替你判断文件是否超限,它只管按你给的 buffer 大小和 count 去拷贝——哪怕文件有 10GB,fread 也照读不误,直到 malloc 失败或进程被 OOM killer 干掉。
实操建议:
- 用
stat()或fstat()先查st_size,再跟你的上限(比如 10MB)比对,超了就拒绝读取 - Windows 下用
GetFileSizeEx(),别信GetFileSize()的高位截断 bug - 注意符号问题:
st_size是off_t,可能为负(比如设备文件),要先判非负再比较 - 不要用
ftell(fseek(file, 0, SEEK_END))获取大小——对管道、socket、/proc 文件会失败或返回 0
std::ifstream 配合 gcount() 分块读更安全
std::ifstream::read() 本身不校验目标 buffer 容量,但配合 gcount() 能实时知道本次读了多少字节,这是控制总读取量的关键。
常见错误现象:用 read(buf, 1024*1024) 循环读,却没在每次读完后累加并检查累计值,结果最后一块虽不足 1MB,但加起来仍超限。
实操建议:
- 预设最大允许字节数(如
const size_t MAX_READ = 10 * 1024 * 1024) - 每次
read()后立刻调gcount(),累加到已读计数器;一旦累计 ≥MAX_READ,立刻 break 并 close 流 - 注意
gcount()返回的是上一次read()实际读到的字节数,不是剩余可读量 - 如果文件编码不确定(比如含 BOM 或 UTF-8 多字节),别用字符计数代替字节计数——限制必须基于字节
mmap 不等于安全,MAP_PRIVATE 仍可能触发 OOM
有人觉得用 mmap 就不用怕 OOM,其实错得离谱。Linux 下 mmap 默认是 MAP_PRIVATE,看似“懒加载”,但当进程真正访问某页时,内核仍需分配物理页——如果映射了 5GB 文件,又逐页 touch,照样被 OOM killer 瞄准。
使用场景:仅适合你明确需要随机访问大文件某几块、且能严格控制访问范围的情况。
实操建议:
- 永远用
open()+fstat()检查文件大小,再决定是否mmap - 避免
PROT_WRITE+MAP_SHARED映射超大文件,写入会同步刷盘并放大内存压力 - 读取后记得
munmap(),别依赖析构——RAII 封装时也要确保异常安全 - macOS 上
mmap对超大文件可能直接失败(ENOMEM),不如老实用分块read
第三方库(如 Boost.Iostreams)默认不限流,得手动套 limiting_filter
Boost.Iostreams 的 file_source 或 mapped_file_source 本身不带大小限制,很多人以为用了库就自动防护,结果还是 OOM。
性能影响:加一层 limiting_filter 几乎无开销,但能硬性截断;不过它只限制“从 source 读出的字节数”,不阻止底层打开超大文件,所以仍需前置 stat。
实操建议:
- 构造
boost::iostreams::filtering_istream时,先 pushboost::iostreams::limiting_filter(MAX_READ) - 再 push
boost::iostreams::file_source("xxx", std::ios_base::in | std::ios_base::binary) - 注意
limiting_filter在read()返回 0 后会抛boost::iostreams::stream_error,要捕获而不是忽略 - 别混用
std::getline()和limiting_filter——换行符可能卡在边界,导致实际读取略超限
真正难防的不是大文件本身,而是文件描述符泄漏后反复 open 同一个超大文件;或者把限制逻辑写在业务层却忘了在异常路径里清理 buffer。这些地方一漏,前面所有检查都白搭。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











