Проблема тонкая, но сводится к:
Не смешивайте входные / выходные вызовы уровня потока и позиционные вызовы с низкоуровневыми системными вызовами на базовом дескрипторе системы.
Вот потенциальное объяснение актуальной проблемы:
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()
на наличие ошибок.
- Также используйте
%zu
для size_t
аргументов.
buffer
необязательно завершается нулем, не используйте %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