文件描述符,文件指针和文件位置指示符之间的关系

时间:2012-09-02 14:53:56

标签: c

我试图理解文件位置指示器在从文件中读取一些字节后如何移动。我有一个名为“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个字节也不起作用。有什么建议吗?

2 个答案:

答案 0 :(得分:3)

使用fseekfread lseekread,但不要混用这两个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)                           = ?