fseek 仅在 fread 调用之后而不是读取后使用?
fseek only working with fread call after rather than read?
我打开一个文件:
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)
returns 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);
来确定与系统句柄关联的文件的长度。系统编辑的长度return为12
,小于流缓冲区大小,fseek()
重置系统句柄位置并将12个字节读入缓冲区,使系统句柄位置为12
,将流的内部文件位置设置为 12。
ftell(fp);
returns 流的内部文件位置,12。这样做是因为流是以二进制模式打开的,不推荐文本文件使用这种模式,因为行尾序列不会在遗留系统上被翻译成换行符 '\n'
)。
fseek(fp, 0L, SEEK_SET);
将流的内部文件位置设置为 0
,这是在当前缓冲的内容中,它不会发出 lseek()
系统调用。
read(fileno(fp), buffer, 100);
无法读取任何内容,因为系统句柄的当前位置是 12,文件末尾。
fread(buffer, 100, 1, fp)
将从缓冲区中读取文件内容,12字节,尝试从文件中读取更多内容,none可用,return读取的字符数, 12.
相反,如果将 1
传递给 fseek()
,会发生以下情况:
fseek(fp, 1L, SEEK_END);
使用系统调用 lseek(fileno(fp), 0L, 2);
来确定与系统句柄关联的文件的长度。系统读取的长度return为12
,因此请求的位置为13,小于流缓冲区大小,fseek()
重置系统句柄位置并尝试从中读取13个字节将文件放入流缓冲区,但文件中只有 12 个字节可用。 fseek
清除缓冲区并发出系统调用 lseek(fileno(fp), 1L, 2);
并将流内部文件位置跟踪为 13。
ftell(fp);
returns 流内部文件位置,即13
.
fseek(fp, 0L, SEEK_SET);
将内部文件位置重置为 0
,并发出系统调用 lseek(fileno(fp), 0L, 0);
,因为该位置在当前流缓冲区之外。
read(fileno(fp), buffer, 100);
从系统句柄当前位置读取文件内容,这也是 0
,因此表现符合预期。
备注:
- 无法保证此行为,因为 C 标准未指定流函数的实现,但它与观察到的行为一致。
- 您应该检查
fseek()
和 ftell()
的 return 值是否失败。
- 也使用
%zu
作为 size_t
个参数。
buffer
不一定是 null 终止的,不要使用 %s
用 printf
打印其内容,使用 %.*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
我打开一个文件:
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)
returns 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);
来确定与系统句柄关联的文件的长度。系统编辑的长度return为12
,小于流缓冲区大小,fseek()
重置系统句柄位置并将12个字节读入缓冲区,使系统句柄位置为12
,将流的内部文件位置设置为 12。ftell(fp);
returns 流的内部文件位置,12。这样做是因为流是以二进制模式打开的,不推荐文本文件使用这种模式,因为行尾序列不会在遗留系统上被翻译成换行符'\n'
)。fseek(fp, 0L, SEEK_SET);
将流的内部文件位置设置为0
,这是在当前缓冲的内容中,它不会发出lseek()
系统调用。read(fileno(fp), buffer, 100);
无法读取任何内容,因为系统句柄的当前位置是 12,文件末尾。fread(buffer, 100, 1, fp)
将从缓冲区中读取文件内容,12字节,尝试从文件中读取更多内容,none可用,return读取的字符数, 12.
相反,如果将 1
传递给 fseek()
,会发生以下情况:
fseek(fp, 1L, SEEK_END);
使用系统调用lseek(fileno(fp), 0L, 2);
来确定与系统句柄关联的文件的长度。系统读取的长度return为12
,因此请求的位置为13,小于流缓冲区大小,fseek()
重置系统句柄位置并尝试从中读取13个字节将文件放入流缓冲区,但文件中只有 12 个字节可用。fseek
清除缓冲区并发出系统调用lseek(fileno(fp), 1L, 2);
并将流内部文件位置跟踪为 13。ftell(fp);
returns 流内部文件位置,即13
.fseek(fp, 0L, SEEK_SET);
将内部文件位置重置为0
,并发出系统调用lseek(fileno(fp), 0L, 0);
,因为该位置在当前流缓冲区之外。read(fileno(fp), buffer, 100);
从系统句柄当前位置读取文件内容,这也是0
,因此表现符合预期。
备注:
- 无法保证此行为,因为 C 标准未指定流函数的实现,但它与观察到的行为一致。
- 您应该检查
fseek()
和ftell()
的 return 值是否失败。 - 也使用
%zu
作为size_t
个参数。 buffer
不一定是 null 终止的,不要使用%s
用printf
打印其内容,使用%.*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