+-
fseek只用fread调用而不是读取?

我打开一个文件:

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;
 }
3
投票

这个问题很棘手,但归结为:

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

以下是对实际问题的可能解释:

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);返回流内部文件位置,即 13fseek(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