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

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

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

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

7 ответов


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

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


статья утверждает fseek(stream, 0, SEEK_END) является неопределенным поведением, ссылаясь на вырванная из контекста сноска.

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

это неопределенное поведение проистекает из сочетания двух пунктах. Во-первых, в §7.19.2 / 5 говорится, что:

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

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

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

это делает fseek(stream, 0, SEEK_END) неопределенное поведение для широко ориентированных потоков. Нет такого правила, как §7.19.2/5 для потоки, ориентированные на байты.

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

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

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

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


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

то есть

fstat(fileno(filePointer), &buf);

здесь filePointer - это FILE *

и

buf is

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

разные ОС предоставляют разные API для этого. Например, в Windows у нас есть:

GetFileAttributes()

в MAC у нас есть:

[[[NSFileManager defaultManager] attributesOfItemAtPath: ошибка someFilePath: nil] размер файла];

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


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


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

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

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

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

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

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

Джон Боулер


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

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

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

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

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

некоторые неопределенные поведения-это области языка, в которых могут быть предоставлены значимые расширения. Платформа заполняет пробел, определяя поведение.

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

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

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

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

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

#include <unistd.h>

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

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

в точку форма:

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

ссылки: