必须用二进制模式打开文件才能用 fseek 精确字节跳转;文本模式下 fseek 偏移量不等于字节数,且仅支持 ftell 返回的合法位置;seekg 同样需注意流状态和编码影响,混用 C/C++ IO 会导致未定义行为。

用 fseek 移动文件指针前必须确认文件是以二进制模式打开的
很多 C++ 程序员在 C 风格文件操作中踩的第一个坑是:用 fopen("data.txt", "r") 打开文本文件后调用 fseek(fp, 10, SEEK_SET),结果偏移量“不准”——尤其在 Windows 上,\r\n 被当作一个字符处理,但底层存储占两个字节,fseek 却按字节跳转,导致后续 fread 读出错乱数据。
解决方法很简单但常被忽略:
- 文本模式(
"r","w")下,fseek的偏移量单位是“文件位置”,不等于字节数,且只允许跳转到ftell返回过的合法位置(比如不能随意跳到中间某字节) - 真正可控的字节级定位,必须用二进制模式:
fopen("data.bin", "rb")或"wb" - 如果原文件是文本但你又必须精确跳转(比如解析固定宽度日志),先用二进制模式打开,自己处理换行符逻辑
seekg 在 std::ifstream 中的偏移量单位始终是字节,但流状态影响实际效果
seekg 看似更“现代”,但它不是无条件生效。常见失效场景包括:
- 流已处于
failbit或eofbit状态(比如之前读到了文件末尾),此时seekg调用会静默失败 —— 必须先调用clear() - 使用
seekg(pos, std::ios::beg)时,pos是从文件开头算的字节偏移,和fseek(fp, pos, SEEK_SET)行为一致 - 但
seekg(0, std::ios::end)后再tellg()得到的是文件总字节数,这在文本文件中才真正可靠;若中间有宽字符或 BOM,需注意编码(如 UTF-8 下 BOM 占 3 字节)
示例:安全地跳转到倒数第 10 字节
std::ifstream fin("log.dat", std::ios::binary);
fin.seekg(0, std::ios::end);
auto size = fin.tellg();
if (size > 10) {
fin.clear(); // 清除可能的 eofbit
fin.seekg(size - 10, std::ios::beg);
char buf[11] = {};
fin.read(buf, 10);
}
混合使用 fseek 和 seekg 会导致未定义行为
C++ 标准明确禁止对同一个文件同时用 C 风格 FILE* 和 C++ 流对象操作 —— 它们各自维护独立的缓冲区和文件位置指示器。例如:
- 用
fopen打开文件并fseek到中间,再用该文件名构造std::ifstream,后者初始位置仍是文件开头,不是fseek的位置 - 反过来,先用
seekg移动,再用fileno()拿到 fd 去lseek,C++ 流内部缓存可能还没刷新,读写会错位 - 唯一安全的混用方式是:完全分离 IO 路径,或用
std::filebuf::fdopen(C++17 起)显式绑定同一底层句柄,并确保同步刷新
偏移量计算要区分“逻辑记录”和“物理字节”,尤其涉及结构体序列化
当你想跳到第 n 个结构体时,别直接写 fseek(fp, n * sizeof(MyStruct), SEEK_SET) —— 这假设结构体没有填充(padding),而编译器会按对齐规则插入空字节。真实布局得用 offsetof 或 static_assert 验证:
- 用
#pragma pack(1)强制紧凑排列(适合跨平台二进制协议),但会牺牲访问性能 - 更稳妥的做法是:把结构体序列化成字节流再写入,读取时也反序列化,而不是直接
fread(&s, 1, sizeof(s), fp) - 如果结构体含指针、虚函数或 STL 容器(如
std::string),绝对不能直接 fseek + fread —— 它们内存布局不可序列化
真正需要随机访问的二进制数据,建议用 mmap(mmap / MapViewOfFile)替代 fseek,避免频繁系统调用和缓冲区管理负担。