fseek работает только с вызовом fread, а не с чтением? - PullRequest
3 голосов
/ 14 апреля 2019

Я открываю файл:

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

1 Ответ

3 голосов
/ 14 апреля 2019

Проблема тонкая, но сводится к:

Не смешивайте входные / выходные вызовы уровня потока и позиционные вызовы с низкоуровневыми системными вызовами на базовом дескрипторе системы.

Вот потенциальное объяснение актуальной проблемы:

  • 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
...