Как получить размер файла в ANSI C без fseek и ftell? - PullRequest
13 голосов
/ 23 марта 2012

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

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

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

Ответы [ 7 ]

14 голосов
/ 23 марта 2012

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

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

6 голосов
/ 23 марта 2012

В статье утверждается, что 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 не позволяет различать такие случаи, поэтому вы застреваете в системных вызовах, если хотите это учитывать.

3 голосов
/ 23 марта 2012

Использовать 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 */
};
2 голосов
/ 17 декабря 2014

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

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

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

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

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

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

Джон Боулер

2 голосов
/ 23 марта 2012

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

2 голосов
/ 23 марта 2012

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

GetFileAttributes ()

В MAC у нас есть:

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

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

0 голосов
/ 02 мая 2012

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

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

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

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

Но неопределенное поведение не означает «плохое поведение»; это просто поведение, для которого стандарт 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 не обеспечивает поведение, то, скорее всего, ничего не дает, в любом случае.

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

#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, не полагаясь на какое-то неопределенное поведение. Примером этого является программа, которая хочет получить столько байтов назад от конца файла.

Ссылки

...