clang_slide

Программирование C — Как получить размер файла?

Как оказалось, узнать размер файла в языке C - совсем нетривиальная задача. В процессе её решения как минимум вы обязательно столкнетесь с переполнением целочисленного типа данных. В данной статье я приведу 4 способа получения размера файла с использованием функций из стандартной библиотеки C, функций из библиотеки POSIX и функций из библиотек Windows.
Способ 1: решение "в лоб" (скомпилируется везде, но работает очень долго)
Мы просто откроем файл в бинарном режиме и в цикле считаем из него байт за байтом.

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

 

Способ 2: с использованием функций fseek и ftell (ограничен для объемных файлов и работает не всегда верно)

Данный способ основан на использовании функций стандартной библиотеки C: fseek и ftell. Что происходит - открываем файл в бинарном режиме, перемещаем внутренний указатель положения в файле сразу в конец с помощью fseek, получаем номер последнего байта с помощью ftell.

Проблем у данного способа несколько.
Первое - это возвращаемый тип функции ftell. У разных компиляторов на разных платформах по разному. Если у вас 32х битная система, то данный способ будет работать только для файлов, размером меньше 2048 Мб, поскольку максимальное значение для возвращаемого функцией типа long там будет 2147483647. На системах с большей разрядностью будет работать лучше, из-за большего значения максимума для long. Но подобная нестабильность будет мешать. Хотя у меня на 64х битой системе на компиляторе gcc данный способ для файлов больше 8 Гб выводил некорректные значения.
Второе - гарантированность работы fseek и ftell. Коротко говоря, на разных платформах работает по-разному. Где то будет точно возвращать значение положения последнего байта, где то будет возвращать неверное значение. То есть точность данного способа негарантированна.

Плюсом является то, что эти функции из стандартной библиотеки - скомпилируется почти везде.

Стоит сказать, что хитрые инженеры из Microsoft придумали функции _fseeki64 и _ftelli64, которые, как понятно из их названия, работают с int64, что решает проблему с размером файла в MSVC под Windows.

 

Способ 3: (под Linux (POSIX))

Данный способ основан на использовании системном вызове fstat с использованием специальной структуры struct stat. Как работает: открываем файл через open() или fopen(), вызываем fstat для дескриптора файла (если открыли через fopen, то в fstat надо положить результат fileno от указателя потока FILE), указав на буферную структуру для результатов, и получаем значения поля буферной структуры st_size.

Гарантированно работает под Linux. Тип поля st_size будет как знаковый int64. Под Windows с использованием Mingw также можно использовать такой способ (с fopen и fileno), но я точно не знаю как там обстоят дела с типом поля структуры st_size.

 

Способ 4: (под Windows (начиная с Windows XP)):

Еще один способ конкретно под системы Windows с помощью функции GetFileSizeEx, которая доступна в системах, начиная с Windows XP. Для систем постарее используйте GetFileSize (но там снова проблема с максимальным размером файла из-за типа данных).

GetFileSizeEx гарантированно работает под Windows. Не работает больше нигде. Тип под размер файла: LONGLONG, который равен размеру знаковому int64. То есть узнаем размер для файлов с максимум 9223372036854775807 байт (до 8589934592 Гб).

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

Свои варианты оставляйте в комментариях, будет интересно посмотреть!

3 thoughts on “Программирование C — Как получить размер файла?

  1. ———————————————————————————————————————————
    Для любых версий Windows начиная с 95 и для DOS версии под 95/NTVDM:
    BY_HANDLE_FILE_INFORMATION FileInformation;
    Status = GetFileInformationByHandle (hFile, &FileInformation);
    if (!Status) … // ошибка

    P.S. DOS программа должна вызывать функцию 71A6h Int 21h LFN API (http://www.delorie.com/djgpp/doc/rbinter/id/07/32.html)
    как итог: DOS программа под NTVDM может (!) работать с файлами >4 Гб на NTFS!!! (проверено)

    ———————————————————————————————————————————
    Linux:
    // работа с файлами >4 Гб — это должно быть до любых .h файлов из GCC
    #define _LARGEFILE64_SOURCE
    #define _FILE_OFFSET_BITS 64
    #define __USE_FILE_OFFSET64
    // это объявляет типы int8_t…int64_t и uint8_t…uint64_t
    #include
    // это объявляет разные типы, NULL, STDIN_FILENO и т.п.
    #include
    int64_t llFileSize;
    struct stat statbuf;

    // получить информацию о файле
    fstat (hFile, &statbuf); // ошибка не проверяется для примера
    llFileSize = statbuf.st_size;
    // llFileSize — статус выполнения: длина файла в байтах, -1LL в случае ошибки
    if (llFileSize == -1LL) … // если ошибка

    это работает на файлах >4 Гб для x32 и x64 Linux
    ———————————————————————————————————————————

    ИТОГ: для DOS, Windows и Linux можно получить 64 битный размер файла (для DOS варианта с некоторыми ограничениями).

    • В примере для Linux исказился текст директивы include:

      // это объявляет типы int8_t…int64_t и uint8_t…uint64_t
      #include [stdint.h]
      // это объявляет разные типы, NULL, STDIN_FILENO и т.п.
      #include [unistd.h]

  2. // рабочий пример для Borland C++ 3.1 (16 разрядный DOS)
    // это будет работать при запуске под Windows 95 и NT4 и выше
    // но длину файла >4 Гб можно получить только на NT платформе (очевидно)

    typedef union _DWORDLONG {
    // порядок важен (в памяти Low идет первым)
    struct {
    DWORD LowPart;
    DWORD HighPart;
    } d;
    struct {
    WORD W0; // LowPart
    WORD W1;
    WORD W2;
    WORD W3; // HighPart
    } w;
    } DWORDLONG;

    … некая функция …
    {
    DWORDLONG nFileSize; // 64 битный тип
    BY_HANDLE_FILE_INFORMATION FileInformation; // Windows95 file information (структура должна быть в стеке!)

    _BX = hFile; // file handle (хэндл открытого файла)
    _DX = (WORD)&FileInformation; // SS:DX — адрес структуры в стеке
    asm {
    push ds
    mov ax,ss
    mov ds,ax // DS:DX -> buffer for file information
    mov ax,0x71A6 // get file info by handle
    stc // это надо для совместимости с версиями DOS до 7.0
    int 0x21
    pop ds
    // CF=0 — успешно
    // CF=1 — ошибка, AX — error code (LFN_NOT_SUPPORTED = 0x7100 если функция не поддерживается)
    jc short __Error // функции LFN не поддерживаются или ошибка
    }
    nFileSize.d.LowPart = FileInformation.nFileSizeLow;
    nFileSize.d.HighPart = FileInformation.nFileSizeHigh;
    // успешно
    }

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">