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,剩下的数据就「丢了」。

为什么会这样?

因为 fseekftell 的偏移量参数和返回值都是 long 类型。在 32 位系统上,long 是 4 字节,有符号,最大正数就是 2,147,483,647。说白了,这是 C 语言标准在几十年前定下的「历史包袱」。

解决方案一:fseeko / ftello(POSIX 标准)

如果你在 Linux、macOS 或者任何 POSIX 兼容系统上开发,fseekoftello 是你的首选。它们用 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 位的。后来才发现是没加这个宏。

个人习惯:我一般在 Makefile 或 CMakeLists.txt 里统一加上 -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
  • 提供对应的函数(fseeko64ftello64lseek64 等)
  • 通过宏定义(如 _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 位后缀」的函数。它们更多是为了向后兼容。

核心知识体系

下面这张图,帮你理清大文件处理的各种方案和关系:

大文件处理方案对比 文件 > 2GB 时指针溢出 方案一:fseeko/ftello 方案二:_fseeki64/_ftelli64 方案三:LFS 规范 POSIX(Linux/macOS) Windows 专用 跨平台规范 off_t 类型,需定义 _FILE_OFFSET_BITS=64 __int64 类型 微软扩展函数 off64_t / fopen64 需定义 _LARGEFILE64_SOURCE 推荐:64 位系统直接用 fseeko + _FILE_OFFSET_BITS=64 跨平台时用条件编译区分 Windows 和 POSIX

实际项目中的避坑指南

我曾经在一个嵌入式 Linux 项目里,需要处理 4GB 以上的视频文件。当时用了 fseeko,编译也加了 -D_FILE_OFFSET_BITS=64,但运行时 ftello 返回的值还是不对。

排查了半天,发现是文件系统的问题——那个嵌入式设备用的老版本 FAT32,单个文件最大只支持 4GB。文件虽然写进去了,但文件系统层就已经截断了。嗯,这里要注意:大文件处理不光看代码,还得看底层文件系统支不支持

警告:FAT32 文件系统单个文件最大 4GB,exFAT 和 NTFS 才支持更大文件。如果你在嵌入式设备上做日志存储,建议用 ext4 或 exFAT。

另一个坑是 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 更直接,也少了一次文件指针的移动。

总结一下

大文件处理的核心就三点:

  1. 别用 fseek/ftell——它们用 long,32 位系统上最大 2GB。
  2. POSIX 用 fseeko/ftello,记得加 _FILE_OFFSET_BITS=64
  3. Windows 用 _fseeki64/_ftelli64,跨平台用条件编译。

至于 LFS,它更像是一个「历史规范」。现在 64 位系统遍地都是,你直接用 fseeko 就足够了。但如果你在维护老旧的 32 位嵌入式系统,LFS 的那些 64 位后缀函数还是能派上用场的。

最后说一句:写代码时多想想「这个文件会不会超过 2GB」。别像我当年那样,等到线上出问题了才回头改。提前用上 64 位接口,省心很多。

核心原则:能用 64 位偏移量,就别用 32 位的。哪怕现在文件很小,保不准哪天就变大了。代码的「防御性」设计,从文件指针开始。


公众号:蓝海资料掘金营,微信deep3321