fseek仅在读取后才进行fread调用,而不是读吗?

时间:2019-04-14 08:38:46

标签: c file-io posix

我打开文件:

FILE *fp = fopen("hello_world.txt", "rb");

其中仅包含内容Hello World!

然后我得到大小并重新设置为开头:

fseek(fp, 0L, SEEK_END);
size_t sz = ftell(fp);
fseek(fp, 0L, SEEK_SET);

当我去执行read时,它似乎不起作用。 read(fileno(fp), buffer, 100)返回0

但是,如果我改为这样做;

fread(buffer, 100, 1, fp)

这确实可以正确地读入缓冲区。

即使是陌生人,如果我将第一个fseek调用的偏移量更改为1,它也可以正常工作(尽管已超出文件末尾)。我想知道为什么会这样。我最初的想法是与清除EOF标志有关,但是我认为至少应该在重新进行fseek时将其重置。不确定fread为什么起作用。 看起来我正在调用某种未定义的行为,因为在不同的计算机上运行时某些情况有所不同,但我不知道为什么。

这是MCVE:

#include <stdio.h>
#include <unistd.h>

int main() {
     FILE *fp = fopen("hello_world.txt", "rb");
     fseek(fp, 0L, SEEK_END); // works fine if offset is 1, but read doesn't get any bytes if offset is 0
     size_t sz = ftell(fp);
     fseek(fp, 0L, SEEK_SET);
     char buffer[100];
     size_t chars_read = read(fileno(fp), buffer, 100);
     printf("Buffer: %s, chars: %lu", buffer, chars_read);
     fclose(fp);
     return 0;
 }

1 个答案:

答案 0 :(得分:3)

这个问题很微妙,但是可以归结为:

  

请勿在底层系统句柄上将流级输入/输出和定位调用与低级系统调用混合在一起。

以下是对实际问题的潜在解释:

  • fseek(fp, 0L, SEEK_END);使用系统调用lseek(fileno(fp), 0L, 2);来确定与系统句柄关联的文件的长度。系统返回的长度为12,小于流缓冲区的大小,fseek()重置系统句柄位置并将12个字节读取到缓冲区中,从而将系统句柄位置保留为12 ,将流的内部文件位置设置为12。
  • ftell(fp);返回流的内部文件位置12。之所以这样做,是因为该流以二进制模式打开,因此不建议将其用于文本文件,因为行尾序列不会转换为换行符{{ 1}}(在旧版系统上)。
  • '\n'将流的内部文件位置设置为fseek(fp, 0L, SEEK_SET);,位于当前缓冲的内容之内,不发出0系统调用。
  • lseek()无法读取任何内容,因为系统句柄的当前位置位于文件末尾12。
  • read(fileno(fp), buffer, 100);将从缓冲区读取文件内容(12个字节),尝试从文件中读取更多内容,没有可用的内容,并返回读取的字符数12。

相反,如果将fread(buffer, 100, 1, fp)传递给1,则会发生以下情况:

  • fseek()使用系统调用fseek(fp, 1L, SEEK_END);来确定与系统句柄关联的文件的长度。系统返回的长度为lseek(fileno(fp), 0L, 2);,因此请求的位置为13,小于流缓冲区的大小,12重置系统句柄位置,并尝试将文件中的13个字节读取到流中缓冲区,但文件中只有12个字节可用。 fseek()清除缓冲区并发出系统调用fseek,并将流内部文件位置保持为13。
  • lseek(fileno(fp), 1L, 2);返回流内部文件位置,即ftell(fp);
  • 13将内部文件位置重置为fseek(fp, 0L, SEEK_SET);,并发出系统调用0,因为该位置在当前流缓冲区之外。
  • lseek(fileno(fp), 0L, 0);从系统句柄当前位置读取文件内容,该位置也是read(fileno(fp), buffer, 100);,因此表现正常。

注意:

  • 不能保证此行为,因为C标准未指定流函数的实现,但与观察到的行为一致。
  • 您应检查0fseek()的返回值是否失败。
  • 还为ftell()参数使用%zu
  • size_t不一定以null结尾,不要使用buffer%s打印其内容,使用printf并将%.*s作为精度值。

这是经过检测的版本:

(int)chars_read

以下是对Linux上系统调用的跟踪,与我的初步解释一致:文件 hello_world.txt 包含#include <stdio.h> #include <unistd.h> #ifndef fileno extern int fileno(FILE *fp); // in case fileno is not declared #endif int main() { FILE *fp = fopen("hello_world.txt", "rb"); if (fp) { fseek(fp, 0L, SEEK_END); long sz = ftell(fp); fseek(fp, 0L, SEEK_SET); char buffer[100]; ssize_t chars_read = read(fileno(fp), buffer, 100); printf("\nread(fileno(fp), buffer, 100) = %zd, Buffer: '%.*s', sz = %zu\n", chars_read, (int)chars_read, buffer, sz); fclose(fp); } fp = fopen("hello_world.txt", "rb"); if (fp) { fseek(fp, 1L, SEEK_END); long sz = ftell(fp); fseek(fp, 0L, SEEK_SET); char buffer[100]; ssize_t chars_read = read(fileno(fp), buffer, 100); printf("\nread(fileno(fp), buffer, 100) = %zd, Buffer: '%.*s', sz = %zu\n", chars_read, (int)chars_read, buffer, sz); fclose(fp); } return 0; } ,不带换行符,总共12个字节:

Hello world!