文件定位:ftell、fseek、rewind 与三个SEEK常量

文件操作里,读写位置是个容易被忽略的细节。我刚开始写嵌入式程序时,总觉得文件读写就是从头到尾顺序来一遍。直到有一次做日志系统,需要在文件中间插入一条记录……嗯,那次折腾了我整整一个下午。

说白了,文件就像一盘磁带。你读到哪里,写到哪里,都由一个“当前位置指针”决定。今天要聊的这几个函数,就是用来控制这个指针的。

ftell:告诉我你在哪

ftell 的作用很简单——返回当前读写位置相对于文件开头的偏移量。单位是字节。

long ftell(FILE *stream);

返回 -1L 表示出错。我习惯在每次 freadfwrite 之后,顺手调一下 ftell 来确认位置是否正确。尤其是在二进制文件里,一个字节的偏差都可能让整个数据结构错位。

实际场景:解析BMP图片文件时,文件头后面紧跟着像素数据。用 ftell 确认文件头读完后位置是否正确,能省去很多调试时间。

fseek:想去哪就去哪

fseek 是文件定位的核心函数。原型如下:

int fseek(FILE *stream, long offset, int whence);

三个参数:文件指针、偏移量、起始位置。返回0表示成功,非0表示失败。

我曾经在一个数据采集项目中,需要跳过文件头直接读取第1000个采样点。用 fseek(fp, 1000 * sizeof(sample_t), SEEK_SET) 一行代码搞定。如果不用定位,你得先读掉前面1000个数据,想想都头大。

小技巧fseek 的 offset 可以是负数。想往回退几个字节?fseek(fp, -5, SEEK_CUR) 就行。

rewind:一键复位

rewind 相当于 fseek(fp, 0L, SEEK_SET),但更简洁。它把读写位置重置到文件开头,同时清除文件结束标志和错误标志。

void rewind(FILE *stream);

注意它没有返回值。如果你需要检查是否成功,还是用 fseek 吧。

我写单元测试时经常用 rewind:写入一些测试数据,然后倒回去读出来验证。省得反复打开关闭文件。

三个SEEK常量:起点、当前位置、终点

常量 数值 含义
SEEK_SET 0 从文件开头算起
SEEK_CUR 1 从当前位置算起
SEEK_END 2 从文件末尾算起

这三个常量定义在 <stdio.h> 里。你想想看,有了它们,定位就变得非常灵活:

  • SEEK_SET:最常用。直接跳到文件某个绝对位置。
  • SEEK_CUR:相对移动。适合“跳过N个字节”这种场景。
  • SEEK_END:常用于获取文件大小。fseek(fp, 0, SEEK_END); size = ftell(fp);

注意fseek 配合 SEEK_END 时,offset 通常为0或负数。正数偏移在有些系统上行为未定义。我见过有人写 fseek(fp, 100, SEEK_END) 想扩展文件,结果程序在不同平台上表现完全不一样。

综合示例:一个简单的文件浏览器

下面这个例子,演示了如何用这三个函数实现文件内容的随机访问:

#include <stdio.h>

int main() {
    FILE *fp = fopen("data.bin", "rb");
    if (!fp) {
        perror("打开文件失败");
        return 1;
    }

    // 获取文件大小
    fseek(fp, 0, SEEK_END);
    long file_size = ftell(fp);
    printf("文件大小: %ld 字节\n", file_size);

    // 回到开头
    rewind(fp);

    // 跳到第100个字节
    fseek(fp, 100, SEEK_SET);
    unsigned char byte;
    fread(&byte, 1, 1, fp);
    printf("第101个字节: 0x%02X\n", byte);

    // 再往后跳50个字节
    fseek(fp, 50, SEEK_CUR);
    fread(&byte, 1, 1, fp);
    printf("再往后50个字节: 0x%02X\n", byte);

    fclose(fp);
    return 0;
}

知识体系图

下面这张图,把文件定位的核心逻辑串起来了:

文件定位核心逻辑 文件流 FILE* ftell() 获取位置 fseek() 设置位置 rewind() 回到开头 SEEK_SET (0) 文件开头 SEEK_CUR (1) 当前位置 SEEK_END (2) 文件末尾 ftell() 返回 long 类型偏移量,失败返回 -1L fseek() 成功返回 0,失败返回非0 rewind() 无返回值,同时清除错误标志

避坑指南

  • 文本模式下的定位:Windows系统里,文本模式下换行符 \n 会被转换成 \r\n,导致 ftell 返回的值和实际字节数对不上。我建议操作二进制文件时,始终用 "rb""wb" 模式。
  • 不要对管道和终端用 fseek:标准输入、管道、socket 这些流不支持随机定位。fseek 会失败返回 -1。我曾经在调试时对着 stdinfseek,结果程序直接卡住。
  • fseek 后记得检查返回值:很多人(包括以前的我)写完 fseek 就不管了。但文件系统可能出错,磁盘可能满,权限可能不够。养成检查返回值的习惯,能省去很多排查时间。

个人习惯:我写文件操作时,会在每个 fseek 后面加个断言:assert(fseek(fp, offset, whence) == 0);。调试阶段开着断言,发布版本关掉。既保证了正确性,又不影响性能。

文件定位就这些内容。说白了就是三个函数加三个常量,但用好了能解决很多实际问题。下次你写日志系统、配置文件解析器、或者二进制数据读取器时,记得用上它们。


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