【问题标题】:fseek only working with fread call after rather than read?fseek 只使用 fread 调用而不是读取?
【发布时间】:2019-09-04 12:09:42
【问题描述】:

我打开一个文件:

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;
 }

【问题讨论】:

  • 为什么不只使用一次 fseek fseek(fp, 13, SEEK_SET);像这样?.. SEEK_SET => 文件开头 SEEK_CUR => 文件指针的当前位置 SEEK_END => 文件结尾
  • 在同一个打开的文件上混合 stdio 和低级读/写是一个非常糟糕的主意。坚持一个或另一个。
  • 你的意思是open()
  • 使用 open、read、lseek fopen、fread、fseek。为什么你认为你需要混合它们?
  • @n.m.作为 C 的新手,我认为我没有意识到它们是不可互换的。我认为如果它们对它们是否混合产生影响,API 会更加区分它们。我正在使用套接字和管道,所以我也一直在使用系统调用。但我想这是一个很好的教训,为什么不混合!

标签: c file-io posix


【解决方案1】:

问题很微妙,但归结为:

不要将流级输入/输出和定位调用与底层系统句柄上的低级系统调用混合。

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

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

相反,如果您将1 传递给fseek(),会发生以下情况:

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

注意事项:

  • 由于 C 标准未指定流函数的实现,因此无法保证此行为,但与观察到的行为一致。
  • 您应该检查fseek()ftell() 的返回值是否失败。
  • 对于size_t 参数也可以使用%zu
  • buffer 不一定以 null 结尾,不要使用 %sprintf 一起打印其内容,使用 %.*s 并将 (int)chars_read 作为精度值传递。

这是一个检测版本:

#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;
}

这是 linux 上系统调用的跟踪,与我的初步解释一致:文件 hello_world.txt 包含 Hello world! 没有换行符,总共 12 个字节:

chqrlie$ strace ./rb612-1
...
<removed system calls related to program startup>
...
open("hello_world.txt", O_RDONLY)       = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=12, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5e356ed000
fstat(3, {st_mode=S_IFREG|0644, st_size=12, ...}) = 0
lseek(3, 0, SEEK_SET)                   = 0
read(3, "Hello world!", 12)             = 12
lseek(3, 12, SEEK_SET)                  = 12
read(3, "", 100)                        = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5e356ec000
write(1, "\n", 1
)                       = 1
write(1, "read(fileno(fp), buffer, 100) = "..., 55read(fileno(fp), buffer, 100) = 0, Buffer: '', sz = 12
) = 55
close(3)                                = 0
munmap(0x7f5e356ed000, 4096)            = 0
open("hello_world.txt", O_RDONLY)       = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=12, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5e356ed000
fstat(3, {st_mode=S_IFREG|0644, st_size=12, ...}) = 0
lseek(3, 0, SEEK_SET)                   = 0
read(3, "Hello world!", 13)             = 12
lseek(3, 1, SEEK_CUR)                   = 13
lseek(3, 0, SEEK_SET)                   = 0
read(3, "Hello world!", 100)            = 12
write(1, "\n", 1
)                       = 1
write(1, "read(fileno(fp), buffer, 100) = "..., 68read(fileno(fp), buffer, 100) = 12, Buffer: 'Hello world!', sz =
) = 68
close(3)                                = 0
munmap(0x7f5e356ed000, 4096)            = 0

【讨论】:

  • 惊人的答案!太感谢了!我永远不会猜到这真的是幕后发生的事情,因为我对 C 还是很陌生。但我确实注意到的一件事是,fseek 调用的返回值为 0,超过了 @987654362 @。根据解释,我认为这会失败,但看起来像it's allowed
  • @rb612:我的第一个解释是错误的,我将答案改写为与 linux 上的观察一致,这恰好显示与 OS/X 相同的行为,但在底层实现和顺序上可能存在差异系统调用。
猜你喜欢
  • 2021-02-19
  • 1970-01-01
  • 1970-01-01
  • 2018-11-18
  • 1970-01-01
  • 1970-01-01
  • 2014-12-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多