Как оказалось, узнать размер файла в языке C - совсем нетривиальная задача. В процессе её решения как минимум вы обязательно столкнетесь с переполнением целочисленного типа данных. В данной статье я приведу 4 способа получения размера файла с использованием функций из стандартной библиотеки C, функций из библиотеки POSIX и функций из библиотек Windows.
Способ 1: решение "в лоб" (скомпилируется везде, но работает очень долго)
Мы просто откроем файл в бинарном режиме и в цикле считаем из него байт за байтом.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#include <stdio.h> #include <stdlib.h> #include <stdint.h> // для int64_t #include <inttypes.h> // для правильного вывода int64_t в printf int64_t getFileSize(const char* file_name){ int64_t _file_size = 0; FILE* fd = fopen(file_name, "rb"); if(fd == NULL){ _file_size = -1; } else{ while(getc(fd) != EOF) _file_size++; fclose(fd); } return _file_size; } #define FILE_PATH "some_file.txt" int main(){ int64_t file_size = getFileSize(FILE_PATH); printf("File size: %" PRId64 "\n", file_size); return 0; } |
Очевидным недостатком способа является скорость работы. Если у нас файл будет на много гигабайт, то только размер файла будет считаться относительно долго (это сколько байт то надо считать?), а надо же еще остальную программу выполнять.
Достоинство такого способа - работать должен на любой платформе. Ну и конечно можно ускорить процесс за счет считывания бОльшего количества байт.
Способ 2: с использованием функций fseek и ftell (ограничен для объемных файлов и работает не всегда верно)
Данный способ основан на использовании функций стандартной библиотеки C: fseek и ftell. Что происходит - открываем файл в бинарном режиме, перемещаем внутренний указатель положения в файле сразу в конец с помощью fseek, получаем номер последнего байта с помощью ftell.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#include <stdio.h> #include <stdlib.h> #include <stdint.h> // для int64_t #include <inttypes.h> // для правильного вывода int64_t в printf int64_t getFileSize(const char* file_name){ int64_t _file_size = 0; FILE* fd = fopen(file_name, "rb"); if(fd == NULL){ _file_size = -1; } else{ fseek(fd, 0, SEEK_END); _file_size = ftello(fd); fclose(fd); } return _file_size; } #define FILE_PATH "some_file.txt" int main(){ int64_t file_size = getFileSize(FILE_PATH); printf("File size: %" PRId64 "\n", file_size); return 0; } |
Проблем у данного способа несколько.
Первое - это возвращаемый тип функции 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
#include <stdio.h> #include <stdlib.h> #include <stdint.h> // для int64_t #include <inttypes.h> // для правильного вывода int64_t в printf #ifdef __linux__ #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #endif int64_t getFileSize(const char* file_name){ int64_t _file_size = 0; struct stat _fileStatbuff; int fd = open(file_name, O_RDONLY); if(fd == -1){ _file_size = -1; } else{ if ((fstat(fd, &_fileStatbuff) != 0) || (!S_ISREG(_fileStatbuff.st_mode))) { _file_size = -1; } else{ _file_size = _fileStatbuff.st_size; } close(fd); } return _file_size; } #define FILE_PATH "some_file.txt" int main(){ int64_t file_size = getFileSize(FILE_PATH); printf("File size: %" PRId64 "\n", file_size); return 0; } |
Гарантированно работает под Linux. Тип поля st_size будет как знаковый int64. Под Windows с использованием Mingw также можно использовать такой способ (с fopen и fileno), но я точно не знаю как там обстоят дела с типом поля структуры st_size.
Способ 4: (под Windows (начиная с Windows XP)):
Еще один способ конкретно под системы Windows с помощью функции GetFileSizeEx, которая доступна в системах, начиная с Windows XP. Для систем постарее используйте GetFileSize (но там снова проблема с максимальным размером файла из-за типа данных).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
#include <stdio.h> #include <stdlib.h> #include <stdint.h> // для int64_t #include <inttypes.h> // для правильного вывода int64_t в printf #ifdef _WIN32 // без этих двух строк не скомпилируется // при использовании функции GetFileSizeEx() #define WINVER 0x0501 #define _WIN32_WINNT WINVER #include <windows.h> #include <sys\stat.h> #endif int64_t getFileSize(const char* file_name){ int64_t _file_size = 0; HANDLE hFile; hFile = CreateFile(TEXT(file_name), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); if(hFile == INVALID_HANDLE_VALUE){ _file_size = -1; } else{ LARGE_INTEGER u_winFsz; GetFileSizeEx(hFile, &u_winFsz); CloseHandle(hFile); _file_size = u_winFsz.QuadPart; } return _file_size; } #define FILE_PATH "some_file.txt" int main(){ int64_t file_size = getFileSize(FILE_PATH); printf("File size: %" PRId64 "\n", file_size); return 0; } |
GetFileSizeEx гарантированно работает под Windows. Не работает больше нигде. Тип под размер файла: LONGLONG, который равен размеру знаковому int64. То есть узнаем размер для файлов с максимум 9223372036854775807 байт (до 8589934592 Гб).
Выводы: лучше использовать платформозависимые способы, которые под конкретную систему выдаст гарантированный корректный результат, а не изобретать велосипеды.
Свои варианты оставляйте в комментариях, будет интересно посмотреть!
———————————————————————————————————————————
Для любых версий 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]
// рабочий пример для 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;
// успешно
}