Как получить размер файла в ANSI C без fseek и ftell?
При поиске способов найти размер файла с именем FILE*
я столкнулся с этой статьей, советуя против него. Вместо этого, похоже, рекомендуется использовать файловые дескрипторы и fstat
.
Однако у меня создалось впечатление, что fstat
, open
и дескрипторы файлов в целом не являются переносимыми (после небольшого поиска я нашел что-то для этого эффект).
Есть ли способ получить размер файла в ANSI C, сохраняя его в соответствии с предупреждениями в этой статье?
Ответы
Ответ 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. Примером этого является программа, которая хочет искать столько байтов назад с конца файла.
Литература:
- Dan Pop in comp.std.c, декабрь 2002: http://groups.google.com/group/comp.std.c/msg/534ab15a7bc4e27e?dmode=source
- Крис Торек, comp.std.c, по вопросу о нестандартных функциях, являющихся поведением undefined, февраль 2002: http://groups.google.com/group/comp.lang.c/msg/2fddb081336543f1?dmode=source
- Chris Engebretson, comp.lang.c, April 1997: http://groups.google.com/group/comp.lang.c/msg/3a3812dbcf31de24?dmode=source
- Бен Пфафф, comp.lang.c, декабрь 1998 г. [Смешной ответ со ссылкой на неопределенность включения нестандартных заголовков]: http://groups.google.com/group/comp.lang.c/msg/73b26e6892a1ba4f?dmode=source
- Лоуренс Кирби, comp.lang.c, сентябрь 1998 г. [Объясняет эффекты нестандартных заголовков]: http://groups.google.com/group/comp.lang.c/msg/c85a519fc63bd388?dmode=source
- Christian Bau, comp.lang.c, сентябрь 1997 г. [Объясняет, как поведение undefined
#include <pascal.h>
может принести ключевое слово pascal для привязки.] http://groups.google.com/group/comp.lang.c/msg/e2762cfa9888d5c6?dmode=source