Как получить размер файла в 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>
один из аварийные люки на языке для выполнения чего-либо на данной платформе.
в точку форма:
- неопределенное поведение не является по своей сути "плохим" и не является недостатком безопасности (хотя, конечно, это может быть! Е. Г. переполнения буфера, связанный с неопределенным поведением в области арифметики указателей и разыменование.)
- замена одного неопределенного поведения другим, только с целью избежать неопределенного поведения, бессмысленна.
- неопределенное поведение-это просто специальный термин, используемый в ISO C для обозначения вещей, которые находятся вне область применения определения ISO C. Это не означает "не определено кем-либо в мире" и не означает, что что-то дефектно.
- полагаться на некоторые неопределенные поведения необходимо для создания большинства реальных, полезных программ, потому что многие расширения предоставляются через неопределенное поведение, включая заголовки и функции платформы.
- неопределенное поведение может быть заменено определениями поведения извне ISO C. Например, POSIX.1 (IEEE 1003.1) серия стандартов определяет поведение, в том числе
<unistd.h>
. Неопределенная программа ISO C может быть хорошо определенной программой POSIX C. - проблемы не может быть решена в C, не полагаясь на какое-то неопределенное поведение. Примером этого является программа, которая хочет искать так много байтов назад от конца файла.
ссылки:
- Дэн поп в комп.станд.с, декабря. Две тысячи два: http://groups.google.com/group/comp.std.c/msg/534ab15a7bc4e27e?dmode=source
- Chris Torek, comp.станд.c, по вопросу о нестандартных функциях, являющихся неопределенным поведением, февраль. 2002: http://groups.google.com/group/comp.lang.c/msg/2fddb081336543f1?dmode=source
- Крис Engebretson, комп.ленг.c, апрель 1997 года:http://groups.google.com/group/comp.lang.c/msg/3a3812dbcf31de24?dmode=source
- Бен Пфафф, компания.ленг.с, декабря 1998 года [Jestful ответ со ссылкой на undefinedness о включении нестандартные заголовки]: http://groups.google.com/group/comp.lang.c/msg/73b26e6892a1ba4f?dmode=source
- Лоуренс Кирби, комп.ленг.c, Sep 1998 [объясняет эффекты нестандартных заголовков]:http://groups.google.com/group/comp.lang.c/msg/c85a519fc63bd388?dmode=source
- Christian Bau, comp.ленг.c, Sep 1997 [объясняет, как неопределенное поведение
#include <pascal.h>
может ввести ключевое слово pascal для linkage.] http://groups.google.com/group/comp.lang.c/msg/e2762cfa9888d5c6?dmode=source