Для указателя файла в C, как измерить расстояние между текущей позицией и концом файла? - PullRequest
1 голос
/ 17 апреля 2020

Для заданного указателя файла (FILE *) возможно ли быстро определить расстояние от текущей позиции до конца файла.

Время, необходимое для определения фактического расстояния, не должно быть зависит от расстояния.

Например, вычитание fpos_t, но fpos_t не является целым числом, его нельзя использовать численно. Есть ли альтернативный способ?

Ответы [ 2 ]

6 голосов
/ 17 апреля 2020

Когда вы впервые открываете файл, вы можете использовать fseek() до go до конца файла (но см. Примечание ниже), затем используйте ftell() чтобы получить позицию и сохранить ее (как размер файла). Затем позвоните с rewind() на go обратно в начало.

Затем возвращаемое значение из любого последующего вызова на ftell() может быть вычтено из вашего сохраненного 'размера', чтобы получить смещение (расстояние) от текущей позиции до конца файла:

// Given a FILE* fp that's just been opened:
fseek(fp, 0, SEEK_END);
long int endpos = ftell(fp);
rewind(fp); // Or you can use fseek(fp, 0, SEEK_SET);
//...
// Later in your code:
long int dtoend = endpos - ftell(pf);

Но обратите внимание, что реализации не требуются для реализации SEEK_END: со страницы cplusplus.com на fseek ссылка выше:

  • Реализациям библиотеки разрешено не оказывать значимой поддержки SEEK_END (поэтому код, использующий ее, не имеет реальной стандартной переносимости).

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

long int dtoend(FILE *fp)
{
    long int curpos = ftell(fp); // Get current position
    fseek(fp, 0, SEEK_END);      // Go to the file's end
    long endpos = ftell(fp);     // Gets the file's size
    fseek(fp, curpos, SEEK_SET); // Restore previous pos
    return endpos - curpos;      // Return the distance!
}


Примечание для использования с большими (> 2 ГБ) файлами: Приведенный выше код использует стандартные функции fseek и ftell, которые используют тип long int (который часто имеет ширину 32 бита) для позиций файла; Чтобы использовать подобный код для больших файлов, существует ряд (хотя и платформо-специфичных c) альтернатив ...

На Windows платформах с использованием MSVC (или совместимых) компиляторов, там функции _ftelli64 и _fseeki64, которые можно использовать практически так же, как и их «стандартные» аналоги; например, путем внесения следующих изменений в приведенный выше код:

//...
    int64_t curpos = _ftelli64(fp); // Get current position
    _fseeki64(fp, 0LL, SEEK_END);   // Go to the file's end
    //... and similar changes elsewhere

В системах Linux 64-разрядные вызовы реализуются как ftello и fseeko, если Вы убедитесь, что до #define _FILE_OFFSET_BITS 64.

Другие платформы / компиляторы могут реализовывать (или оба) из вышеперечисленного или иметь некоторые другие, очень похожие 64- битовые замены.


Примечание # 2 - Обработка ошибок: Как указывается в комментариях, вызов fseek с SEEK_END в качестве аргумента origin может завершиться ошибкой в ​​ряде различных обстоятельств; например, если указатель файла равен stdin, если он относится к потоку канала, или (в некоторых системах), если файл открывается в текстовом режиме . Для обработки таких случаев нужно действительно проверить возвращаемое значение вызова fseek, которое будет ненулевым , если оно не удалось. Итак, вот 64-битная версия функции dtoend с такой обработкой ошибок (обратите внимание, что для компиляторов, отличных от MSVC или GNU, вам нужно будет добавить соответствующие макросы определения для bigseek и bigtell функций):
#include <stdio.h>
#include <stdint.h>

#if defined (_MSC_VER) // MSVC/Windows...
    #define bigtell _ftelli64
    #define bigseek _fseeki64
#elif defined (__GNUC__) // GNU/Linux...
    #define _FILE_OFFSET_BITS 64
    #define bigtell ftello
    #define bigseek fseeko
//
// Feel free to add other compiler/platform implementations
//

#else // Unknown platform/compiler - likely to cause warnings!
    #define bigtell ftell
    #define bigseek fseek 
#endif

int64_t dtoend(FILE* fp)
{
    int64_t curpos = bigtell(fp);                   // Saves the file's current position
    if (bigseek(fp, 0LL, SEEK_END) != 0) return -1; // -1 can ONLY be an error condition
    int64_t endpos = bigtell(fp);                   // Retrieve file size (end position)
    bigseek(fp, curpos, SEEK_SET);                  // Restore previously saved position
    return endpos - curpos;                         // Subtract to get distance from end
}

С той же cplusplus.com страница, на которую ссылаются выше:

Для потоков, открытых в В текстовом режиме смещение должно быть либо нулем, либо значением, возвращаемым предыдущим вызовом ftell, а origin обязательно должно быть SEEK_SET. Если функция вызывается с другими значениями для этих аргументов, поддержка зависит от конкретной системы и реализации библиотеки (непереносимой).

3 голосов
/ 17 апреля 2020

Например, вычитание fpos_t, но fpos_t не является целым числом, его нельзя использовать численно. Есть ли альтернативный способ?

Нет, вы не можете.

Структура FILE не знает, как долго файл находится, и поэтому не имеет простого способа найти это расстояние. Файл рассматривается как дорога - вы поворачиваете на дорогу и едете вперед, и когда дорога заканчивается, вы узнаете. Но нет никаких признаков, указывающих, насколько длиннее дорога.

Вы можете задать ОС отдельно, с stat или подобным. Но обратите внимание, что FILE даже не всегда ссылается на файл с определенным концом - это может быть stdin, поступающий из канала, и размер в то время совершенно неизвестен.

...