gpt4 book ai didi

c - fseek 仅在 fread 调用之后而不是读取后使用?

转载 作者:行者123 更新时间:2023-12-04 05:55:04 24 4
gpt4 key购买 nike

我打开一个文件:

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

最佳答案

问题很微妙,但归结为:

Do not mix stream level input/output and positioning calls with low level system calls on the underlying system handle.

这里是对实际问题的可能解释:

  • fseek(fp, 0L, SEEK_END); 使用系统调用 lseek(fileno(fp), 0L, 2); 来确定关联文件的长度与系统句柄。系统返回的长度为12,小于流缓冲区大小,fseek()重新设置系统句柄位置并将12字节读入缓冲区,让系统handle position at 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() 的返回值是否失败。
  • 还使用 %zu 作为 size_t 参数。
  • 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 仅在 fread 调用之后而不是读取后使用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55673446/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com