Подтвердить что ты не робот

Как получить размер файла в ANSI C без fseek и ftell?

При поиске способов найти размер файла с именем FILE* я столкнулся с этой статьей, советуя против него. Вместо этого, похоже, рекомендуется использовать файловые дескрипторы и fstat.

Однако у меня создалось впечатление, что fstat, open и дескрипторы файлов в целом не являются переносимыми (после небольшого поиска я нашел что-то для этого эффект).

Есть ли способ получить размер файла в ANSI C, сохраняя его в соответствии с предупреждениями в этой статье?

4b9b3361

Ответ 1

В стандарте C танец fseek/ftell - это почти единственная игра в городе. Все, что вы делаете, зависит, по крайней мере, от конкретной среды, в которой работает ваша программа. К сожалению, у этого танца также есть свои проблемы, как описано в статьях, которые вы связали.

Я думаю, вы всегда можете прочитать все из файла до EOF и отслеживать по пути - например, с fread().

Ответ 2

В статье утверждается, что fseek(stream, 0, SEEK_END) - это поведение undefined, ссылаясь на внеконтективную сноску.

Сноска появляется в тексте, посвященном широко ориентированным потокам, которые представляют собой потоки, которые первая операция, выполняемая над ними, является операцией с широкими символами.

Это поведение undefined проистекает из комбинации двух абзацев. В первом разделе 7.19.2/5 говорится, что:

- Бинарные широко ориентированные потоки имеют ограничения на размещение файлов, которые приписываются как текстовым, так и двоичным потокам.

И ограничения для позиционирования файлов с текстовыми потоками (§7.19.9.2/4):

Для текстового потока либо offset должен быть равен нулю, либо offset должно быть значением, возвращенным более ранним успешным вызовом функции ftell в потоке, связанном с тем же файлом, и whence должно быть SEEK_SET.

Это делает поведение fseek(stream, 0, SEEK_END) undefined для широко ориентированных потоков. Для байт-ориентированных потоков не существует такого правила, как §7.19.2/5.

Кроме того, когда в стандарте говорится:

Бинарный поток не нуждается в значимой поддержке вызовов fseek с whence значением SEEK_END.

Это не значит, что это поведение undefined. Но если поток поддерживает его, это нормально.

По-видимому, это существует для того, чтобы двоичные файлы могли иметь грубую размерность, т.е. для того, чтобы размер был числом секторов диска, а не числом байтов, и, как таковое, позволяет неожиданно указать количество нулей в конце бинарных файлов. SEEK_END в этом случае не может быть обоснованной. Другие примеры включают в себя каналы или бесконечные файлы, такие как /dev/zero. Тем не менее, стандарт C не дает возможности различать такие случаи, поэтому вы зацикливаетесь на зависящих от системы вызовах, если хотите это считать.

Ответ 3

Использовать fstat - требует дескриптор файла - может получить это из fileno из FILE* - Следовательно, размер находится в вашем понимании вместе с другими деталями.

то есть.

fstat(fileno(filePointer), &buf);

Где filePointer - это FILE *

и

buf

struct stat {
    dev_t     st_dev;     /* ID of device containing file */
    ino_t     st_ino;     /* inode number */
    mode_t    st_mode;    /* protection */
    nlink_t   st_nlink;   /* number of hard links */
    uid_t     st_uid;     /* user ID of owner */
    gid_t     st_gid;     /* group ID of owner */
    dev_t     st_rdev;    /* device ID (if special file) */
    off_t     st_size;    /* total size, in bytes */
    blksize_t st_blksize; /* blocksize for file system I/O */
    blkcnt_t  st_blocks;  /* number of 512B blocks allocated */
    time_t    st_atime;   /* time of last access */
    time_t    st_mtime;   /* time of last modification */
    time_t    st_ctime;   /* time of last status change */
};

Ответ 4

разные ОС предоставляют для этого различные apis. Например, в окнах мы имеем:

GetFileAttributes()

В MAC мы имеем:

[[[NSFileManager defaultManager] attributesOfItemAtPath: someFilePath error: nil] fileSize];

Но исходный метод только для fread и fseek: Как получить размер файла в C?

Ответ 5

Вы не всегда можете избежать написания кода, специфичного для платформы, особенно когда вам приходится иметь дело с вещами, которые являются функцией платформы. Размер файлов - это функция файловой системы, поэтому, как правило, я бы использовал собственный API файловой системы, чтобы получить эту информацию по танцу fseek/ftell. Я бы создал свою собственную общую оболочку вокруг него, чтобы не загрязнять логику приложения конкретными деталями платформы и упростить перенос кода.

Ответ 6

Резюме состоит в том, что вы должны использовать fseek/ftell, потому что нет альтернативы (даже специфичных для реализации), которая лучше.

Основная проблема заключается в том, что "размер" файла в байтах не всегда совпадает с длиной данных в файле и что в некоторых случаях длина данных недоступна.

Пример POSIX - это то, что происходит, когда вы записываете данные на устройство; операционная система знает только размер устройства. После того, как данные были записаны и закрыт (FILE *), нет записи длины записанных данных. Если устройство открыто для чтения, то подход fseek/ftell либо сбой, либо даст вам размер всего устройства.

Когда комитет ANSI-C заседал в конце 1980 года рядом операционных систем, которые участники помнили, просто не сохранял длину данных в файле; скорее, они сохранили блоки диска в файле и предположили, что что-то в данных его прервало. Поток "text" представляет это. Открытие "двоичного" потока в этих файлах показывает не только байты волшебного терминатора, но и любые байты за его пределами, которые никогда не были записаны, но оказались в одном и том же блоке диска.

Следовательно, стандарт C-90 был написан так, чтобы было правильно использовать трюк fseek; результат - это соответствующая программа, но результат может быть не таким, каким вы ожидаете. Поведение этой программы не является "undefined" в определении C-90, и оно не является "реализацией" (поскольку в UN * X оно зависит от файла). Это также недействительно. Скорее вы получаете число, на которое нельзя полностью положиться, или, может быть, в зависимости от параметров fseek, -1 и errno.

На практике, если трюк преуспевает, вы получаете число, которое включает по крайней мере все данные, и это, вероятно, то, что вы хотите, и если трюк не срабатывает, это почти наверняка есть кто-то другой.

Джон Боулер

Ответ 7

В статье есть небольшая проблема с логикой.

Он (правильно) указывает, что определенное использование функций C имеет поведение, которое не определено ISO C. Но тогда, чтобы избежать этого поведения undefined, в статье предлагается решение: замените это использование на определенные платформой функции, К сожалению, использование специфичных для платформы функций также undefined в соответствии с ISO C. Поэтому совет не решает проблему поведения undefined.

Цитата в моей копии стандарта 1999 года подтверждает, что предполагаемое поведение действительно undefined:

Бинарный поток не нуждается в значимой поддержке вызовов fseek с значением whence SEEK_END. [ISO 9899: 1999 7.19.9.2, пункт 3].

Но поведение undefined не означает "плохое поведение"; это просто поведение, для которого стандарт ISO C не дает определения. Не все поведения undefined одинаковы.

Некоторые действия undefined являются областями на языке, где могут быть предоставлены значимые расширения. Платформа заполняет пробел, определяя поведение.

Предоставление рабочего fseek, которое можно искать с SEEK_END, является примером расширения вместо поведения undefined. Можно подтвердить, поддерживает ли данная платформа fseek от SEEK_END, и если это предусмотрено, то это нормально использовать.

Предоставление отдельной функции, такой как lseek, также является расширением вместо поведения undefined (поведение undefined вызова функции, которая не находится в ISO C и не определена в программе C). Это нормально использовать, если доступно.

Обратите внимание, что на тех платформах, которые имеют такие функции, как POSIX lseek, также, вероятно, будет ISO C fseek, который работает с SEEK_END. Также обратите внимание, что на платформах, где fseek в двоичном файле не может искать от SEEK_END, вероятная причина в том, что это невозможно сделать (API не может быть предоставлен для этого, и именно поэтому функция библиотеки C fseek не может его поддерживать).

Итак, если fseek обеспечивает желаемое поведение на данной платформе, то ничего не нужно делать с программой; это пустая трата усилий, чтобы изменить ее, чтобы использовать специальную функцию этой платформы. С другой стороны, если fseek не обеспечивает поведение, то, скорее всего, ничего не делает.

Обратите внимание, что даже включая нестандартный заголовок, который не находится в программе, это поведение undefined. (Без указания определения поведения.) Например, если в программе C появляется следующее:

#include <unistd.h>

после этого поведение не определяется. [См. ссылки ниже.]. Поведение директивы предварительной обработки #include определяется, конечно. Но это создает две возможности: либо заголовок <unistd.h> не существует, и в этом случае требуется диагностика. Или заголовок существует. Но в этом случае содержимое неизвестно (насколько это касается ISO C, такой заголовок не документирован для библиотеки). В этом случае директива include включает неизвестный фрагмент кода, включающий его в блок перевода. Невозможно определить поведение неизвестного фрагмента кода.

#include <platform-specific-header.h> является одним из экранов выходов на языке для выполнения каких-либо действий на данной платформе.

В виде точки:

  • Undefined поведение по своей сути не является "плохим", а не по своей сути недостатком безопасности (хотя, конечно, это может быть! Например, переполнение буфера, связанное с поведениями undefined в области арифметики указателей и разыменований.)
  • Замена одного поведения undefined другим, только с целью избежать поведения undefined, бессмысленна.
  • Undefined поведение - это просто специальный термин, используемый в ISO C, чтобы обозначать вещи, которые не входят в сферу определения ISO C. Это не означает "не определено кем-либо в мире" и не подразумевает, что что-то является дефектным.
  • Полагаясь на некоторые действия undefined, необходимо сделать большинство реальных и полезных программ, потому что многие расширения предоставляются с помощью поведения undefined, включая заголовки и функции платформы. Поведение
  • Undefined может быть заменено определениями поведения вне стандарта ISO C. Например, ряд стандартов POSIX.1 (IEEE 1003.1) определяет поведение включения <unistd.h>. Программа undefined ISO C может быть четко определенной программой POSIX C.
  • Некоторые проблемы не могут быть решены в C, не полагаясь на какое-то поведение undefined. Примером этого является программа, которая хочет искать столько байтов назад с конца файла.

Литература: