文件定位:ftell、fseek、rewind 与三个SEEK常量
文件操作里,读写位置是个容易被忽略的细节。我刚开始写嵌入式程序时,总觉得文件读写就是从头到尾顺序来一遍。直到有一次做日志系统,需要在文件中间插入一条记录……嗯,那次折腾了我整整一个下午。
说白了,文件就像一盘磁带。你读到哪里,写到哪里,都由一个“当前位置指针”决定。今天要聊的这几个函数,就是用来控制这个指针的。
ftell:告诉我你在哪
ftell 的作用很简单——返回当前读写位置相对于文件开头的偏移量。单位是字节。
long ftell(FILE *stream);
返回 -1L 表示出错。我习惯在每次 fread 或 fwrite 之后,顺手调一下 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;
}
知识体系图
下面这张图,把文件定位的核心逻辑串起来了:
避坑指南
- 文本模式下的定位:Windows系统里,文本模式下换行符
\n会被转换成\r\n,导致ftell返回的值和实际字节数对不上。我建议操作二进制文件时,始终用"rb"或"wb"模式。 - 不要对管道和终端用 fseek:标准输入、管道、socket 这些流不支持随机定位。
fseek会失败返回 -1。我曾经在调试时对着stdin调fseek,结果程序直接卡住。 - fseek 后记得检查返回值:很多人(包括以前的我)写完
fseek就不管了。但文件系统可能出错,磁盘可能满,权限可能不够。养成检查返回值的习惯,能省去很多排查时间。
个人习惯:我写文件操作时,会在每个 fseek 后面加个断言:assert(fseek(fp, offset, whence) == 0);。调试阶段开着断言,发布版本关掉。既保证了正确性,又不影响性能。
文件定位就这些内容。说白了就是三个函数加三个常量,但用好了能解决很多实际问题。下次你写日志系统、配置文件解析器、或者二进制数据读取器时,记得用上它们。
公众号:蓝海资料掘金营,微信deep3321