22、大文件处理:fseeko/ftello、_fseeki64/_ftelli64(Windows)、LFS(Large File Support)
说实话,文件操作这块,很多初学者甚至工作两三年的朋友,都容易栽在一个坑里——文件太大,指针不够用。
我记得刚入行那会儿,接手一个嵌入式日志分析工具。设备跑了一周,生成了个 3.2GB 的日志文件。我兴冲冲地用 fseek 跳到文件末尾想算大小,结果返回了个负数。当时我就懵了。查了半天才发现,fseek 用的是 long 类型,在 32 位系统上最大只能表示 2GB 左右。超过这个数,它就「溢出」了。
嗯,这就是我们今天要聊的核心——大文件处理。
为什么普通 fseek/ftell 不够用?
先看个简单的例子:
#include <stdio.h>
int main() {
FILE *fp = fopen("bigfile.bin", "rb");
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
printf("File size: %ld\n", size);
fclose(fp);
return 0;
}
这段代码在文件小于 2GB 时完全没问题。但一旦文件超过 2GB,ftell 返回的 long 就装不下了。你想想看,一个 4GB 的文件,long 只能表示到 2^31-1 ≈ 2.1GB,剩下的数据就「丢了」。
为什么会这样?
因为 fseek 和 ftell 的偏移量参数和返回值都是 long 类型。在 32 位系统上,long 是 4 字节,有符号,最大正数就是 2,147,483,647。说白了,这是 C 语言标准在几十年前定下的「历史包袱」。
解决方案一:fseeko / ftello(POSIX 标准)
如果你在 Linux、macOS 或者任何 POSIX 兼容系统上开发,fseeko 和 ftello 是你的首选。它们用 off_t 类型代替了 long,而 off_t 在 64 位系统上就是 8 字节,能表示到 9EB 级别的文件。
#include <stdio.h>
int main() {
FILE *fp = fopen("bigfile.bin", "rb");
fseeko(fp, 0, SEEK_END);
off_t size = ftello(fp);
printf("File size: %lld\n", (long long)size);
fclose(fp);
return 0;
}
这里有个关键点:编译时可能需要定义 _FILE_OFFSET_BITS=64。我在项目中遇到过好几次,代码写得没问题,但编译出来 off_t 还是 32 位的。后来才发现是没加这个宏。
-D_FILE_OFFSET_BITS=64,省得每个文件都写一遍。
解决方案二:_fseeki64 / _ftelli64(Windows)
Windows 这边,情况稍微不同。微软提供了 _fseeki64 和 _ftelli64,它们用 __int64 作为偏移量类型,直接支持 64 位文件指针。
#include <stdio.h>
int main() {
FILE *fp = fopen("bigfile.bin", "rb");
_fseeki64(fp, 0, SEEK_END);
__int64 size = _ftelli64(fp);
printf("File size: %lld\n", size);
fclose(fp);
return 0;
}
注意,这些函数是微软的扩展,不是标准 C。所以跨平台代码里,你得用条件编译来处理:
#ifdef _WIN32
#define fseeko _fseeki64
#define ftello _ftelli64
#define off_t __int64
#else
#define _FILE_OFFSET_BITS 64
#endif
我曾经在一个跨平台项目里这么干过,效果还不错。但要注意,Windows 上 fopen 打开大文件时,最好加上 "b" 标志(二进制模式),否则某些情况下会有奇怪的行为。
解决方案三:LFS(Large File Support)
LFS 其实不是一个具体的函数,而是一套规范。它定义了如何让 32 位系统也能处理大于 2GB 的文件。核心思路就是:
- 使用 64 位的偏移量类型(
off64_t) - 提供对应的函数(
fseeko64、ftello64、lseek64等) - 通过宏定义(如
_LARGEFILE64_SOURCE)来启用这些接口
在 Linux 上,你可以这样用:
#define _LARGEFILE64_SOURCE
#include <stdio.h>
int main() {
FILE *fp = fopen64("bigfile.bin", "rb");
fseeko64(fp, 0, SEEK_END);
off64_t size = ftello64(fp);
printf("File size: %lld\n", (long long)size);
fclose(fp);
return 0;
}
不过说实话,现在 64 位系统已经很普及了。我个人建议:直接用 fseeko + _FILE_OFFSET_BITS=64 就够了,没必要特意用 fopen64 这些「64 位后缀」的函数。它们更多是为了向后兼容。
核心知识体系
下面这张图,帮你理清大文件处理的各种方案和关系:
实际项目中的避坑指南
我曾经在一个嵌入式 Linux 项目里,需要处理 4GB 以上的视频文件。当时用了 fseeko,编译也加了 -D_FILE_OFFSET_BITS=64,但运行时 ftello 返回的值还是不对。
排查了半天,发现是文件系统的问题——那个嵌入式设备用的老版本 FAT32,单个文件最大只支持 4GB。文件虽然写进去了,但文件系统层就已经截断了。嗯,这里要注意:大文件处理不光看代码,还得看底层文件系统支不支持。
另一个坑是 fseek 的 SEEK_END 行为。有些文件系统(比如某些 NFS 挂载点)对 SEEK_END 的支持不完善,返回的大小可能不准确。我建议:如果只是想知道文件大小,用 stat() 或 fstat() 更可靠。
#include <sys/stat.h>
int main() {
struct stat st;
stat("bigfile.bin", &st);
printf("File size: %lld\n", (long long)st.st_size);
return 0;
}
stat 里的 st_size 类型是 off_t,同样受 _FILE_OFFSET_BITS 影响。这个方法比 fseek + ftell 更直接,也少了一次文件指针的移动。
总结一下
大文件处理的核心就三点:
- 别用
fseek/ftell——它们用long,32 位系统上最大 2GB。 - POSIX 用
fseeko/ftello,记得加_FILE_OFFSET_BITS=64。 - Windows 用
_fseeki64/_ftelli64,跨平台用条件编译。
至于 LFS,它更像是一个「历史规范」。现在 64 位系统遍地都是,你直接用 fseeko 就足够了。但如果你在维护老旧的 32 位嵌入式系统,LFS 的那些 64 位后缀函数还是能派上用场的。
最后说一句:写代码时多想想「这个文件会不会超过 2GB」。别像我当年那样,等到线上出问题了才回头改。提前用上 64 位接口,省心很多。
核心原则:能用 64 位偏移量,就别用 32 位的。哪怕现在文件很小,保不准哪天就变大了。代码的「防御性」设计,从文件指针开始。
公众号:蓝海资料掘金营,微信deep3321