我试图理解文件位置指示器在从文件中读取一些字节后如何移动。我有一个名为“filename.dat”的文件,其中包含一行:“abcdefghijklmnopqrstuvwxyz”(不带引号)。
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main () {
int fd = open("filename.dat", O_RDONLY);
FILE* fp = fdopen(fd,"r");
printf("ftell(fp): %ld, errno = %d\n", ftell(fp), errno);
fseek(fp, 5, SEEK_SET); // advance 5 bytes from beginning of file
printf("file position indicator: %ld, errno = %d\n", ftell(fp), errno);
char buffer[100];
int result = read(fd, buffer, 4); // read 4 bytes
printf("result = %d, buffer = %s, errno = %d\n", result, buffer, errno);
printf("file position indicator: %ld, errno = %d\n", ftell(fp), errno);
fseek(fp, 3, SEEK_CUR); // advance 3 bytes
printf("file position indicator: %ld, errno = %d\n", ftell(fp), errno);
result = read(fd, buffer, 6); // read 6 bytes
printf("result = %d, buffer = %s, errno = %d\n", result, buffer, errno);
printf("file position indicator: %ld\n", ftell(fp));
close(fd);
return 0;
}
ftell(fp): 0, errno = 0
file position indicator: 5, errno = 0
result = 4, buffer = fghi, errno = 0
file position indicator: 5, errno = 0
file position indicator: 8, errno = 0
result = 0, buffer = fghi, errno = 0
file position indicator: 8
我不明白为什么第二次尝试使用read
时,我没有从文件中获取任何字节。另外,当我使用read
从文件中读取内容时,为什么文件位置指示器不会移动?在第二个fseek
上,提前4个字节而不是3个字节也不起作用。有什么建议吗?
答案 0 :(得分:3)
使用fseek
和fread
或 lseek
和read
,但不要混用这两个API,它将无效。
FILE*
有自己的内部缓冲区。 fseek
可能会也可能不会移动内部缓冲区指针。不能保证真实的文件位置指示符(lseek
负责的指示符)会发生变化,如果确实如此,则不知道它有多少。
答案 1 :(得分:1)
首先要注意的是,read调用将chars读入原始缓冲区,但printf()期望为%s参数传递以null结尾的字符串。您没有显式添加空终止符字节,因此您的程序可能会在缓冲区的前4个字节后打印垃圾,但您很幸运,您的编译器已将缓冲区初始化为零,因此您没有注意到此问题。
此程序中的基本问题是您将高级缓冲FILE *调用与低级文件描述符调用混合在一起,这将导致不可预测的行为。 FILE结构包含一个缓冲区和几个int,以支持更有效和方便地访问文件描述符后面的文件。
基本上所有的f *()调用(fopen(),fread(),fseek(),fwrite())都希望所有的I / O都是通过F *结构调用f *()完成的,所以FILE结构中的缓冲区和索引值有效。低级调用(read(),write(),open(),close(),seek())完全忽略FILE结构。
我对你的程序进行了操作。 strace实用程序记录进程所做的所有系统调用。我已经省略了open()调用之前所有无趣的内容。
这是您的公开电话:
open("filename.dat", O_RDONLY) = 3
这是fdopen()发生的地方。 brk调用是内存分配的证据,大概是像malloc(sizeof(FILE))。
fcntl64(3, F_GETFL) = 0 (flags O_RDONLY)
brk(0) = 0x83ea000
brk(0x840b000) = 0x840b000
fstat64(3, {st_mode=S_IFREG|0644, st_size=26, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7728000
这可能是ftell()的效果,或者只是fdopen的最后一部分,我不确定。
_llseek(3, 0, [0], SEEK_CUR) = 0
这是第一个printf。
write(1, "ftell(fp): 0, errno = 0\n", 24) = 24
这是第一个fseek,它决定了在文件中找到位置5的最简单方法是只读取5个字节并忽略它们。
_llseek(3, 0, [0], SEEK_SET) = 0
read(3, "abcde", 5) = 5
这是第三个printf。请注意,没有ftell()调用的证据。 ftell()使用FILE结构中的信息,该结构声称是准确的,因此不需要系统调用。
write(1, "file position indicator: 5, errn"..., 38) = 38
这是你的read()调用。现在,操作系统文件句柄位于第9位,但FILE结构认为它仍处于第5位。
read(3, "fghi", 4) = 4
带有ftell指示位置5的第三和第四个printf。
write(1, "result = 4, buffer = fghi, errno"..., 37) = 37
write(1, "file position indicator: 5, errn"..., 38) = 38
这是fseek(fp,3,SEEK_CUR)调用。 fseek()决定将SEEK_SET返回到文件的开头,并将整个内容读入FILE struct的4k缓冲区。因为它“知道”它位于第5位,所以它“知道”它现在必须在第8位。由于文件只有26个字节长,因此os文件位置现在为eof。
_llseek(3, 0, [0], SEEK_SET) = 0
read(3, "abcdefghijklmnopqrstuvwxyz", 4096) = 26
第五次印刷。
write(1, "file position indicator: 8, errn"..., 38) = 38
这是你的第二次read()调用。由于文件句柄位于eof,因此它读取0个字节。它不会改变缓冲区中的任何内容。
read(3, "", 6) = 0
第六次和第七次打印电话。
write(1, "result = 0, buffer = fghi, errno"..., 37) = 37
write(1, "file position indicator: 8\n", 27) = 27
你的close()调用,以及进程退出。
close(3) = 0
exit_group(0) = ?