1

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

4

2 回答 2

3

使用fseekandfread lseekand read,但不要混合使用这两个 API,它不会起作用。

AFILE*有自己的内部缓冲区。fseek可能只移动也可能不移动内部缓冲区指针。不能保证真正的文件位置指示器(lseek负责的)会发生变化,如果发生变化,也不知道变化了多少。

于 2012-09-02T15:25:43.730 回答
1

首先要注意的是 read 调用将字符读入原始缓冲区,但 printf() 期望为 %s 参数传递以空字符结尾的字符串。您没有显式添加空终止符字节,因此您的程序可能会在缓冲区的前 4 个字节之后打印垃圾,但是您很幸运,并且您的编译器已将缓冲区初始化为零,因此您没有注意到这个问题。

该程序的基本问题是您将高级缓冲 FILE * 调用与低级文件描述符调用混合在一起,这将导致不可预知的行为。FILE 结构包含一个缓冲区和几个整数,以支持更有效和方便地访问文件描述符后面的文件。

基本上所有 f*() 调用(fopen()、fread()、fseek()、fwrite())都期望所有 I/O 都将通过使用 FILE 结构的 f*() 调用来完成,因此缓冲区和FILE 结构中的索引值将是有效的。低级调用(read()、write()、open()、close()、seek())完全忽略 FILE 结构。

我在你的程序上运行了 strace。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

第三个和第四个 printf 带有 ftell 指示位置 5。

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 结构的 4k 缓冲区。因为它“知道”它在位置 5,它“知道”它现在必须在位置 8。由于文件只有 26 字节长,因此 os 文件位置现在位于 eof。

_llseek(3, 0, [0], SEEK_SET)            = 0
read(3, "abcdefghijklmnopqrstuvwxyz", 4096) = 26

第五个 printf。

write(1, "file position indicator: 8, errn"..., 38) = 38

这是您的第二次 read() 调用。由于文件句柄位于 eof,因此它读取 0 个字节。它不会改变缓冲区中的任何内容。

read(3, "", 6)                          = 0

第六个和第七个 printf 调用。

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)                           = ?
于 2012-09-02T16:28:55.197 回答