Стоит ли проверять возвращаемое значение fseek () при расчете длины файла? - PullRequest
1 голос
/ 21 октября 2019

У меня есть этот идиоматический фрагмент для получения длины двоичного файла:

    fseek(my_file, 0, SEEK_END);
    const size_t file_size = ftell(my_file);

... Я знаю, что педантичный fseek(file, 0, SEEK_END) имеет неопределенное поведение для двоичного потока [ 1 ] - но, честно говоря, на платформах, где это проблема, у меня тоже нет fstat(), и в любом случае это тема для другого вопроса ...

Мой вопрос: должен ли япроверить возвращаемое значение fseek() в этом случае?

    if (fseek(my_file, 0, SEEK_END)) {

        return 1;

    }

    const size_t file_size = ftell(my_file);

Я никогда не видел, чтобы fseek() был проверен в таком случае, и я также задаюсь вопросом, какая ошибка fseek() могла бы когда-либо случитьсявернуться сюда.

РЕДАКТИРОВАТЬ:

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

int fsize (FILE * const file, size_t * const size) {

    long int ftell_retval;

    if (fseek(file, 0, SEEK_END) || (ftell_retval = ftell(file)) < 0) {

        /*  Error  */
        *size = 0;
        return 1;

    }

    *size = (size_t) ftell_retval;
    return 0;

}

Так что, когда нам нужно узнать длину файла, мы можем просто сделать:

size_t file_size;

if (fsize(my_file, &file_size)) {

    fprintf(stderr, "Error calculating the length of the file\n");
    return 1;

}

Ответы [ 4 ]

3 голосов
/ 21 октября 2019

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

Вы можетепрочитайте следующую ссылку о возвращаемом значении fseek: fseek в разделе возвращаемое значение .

Этот оператор if пренебрежим в конвейере кода, покаоблегчить лечение проблем, когда они возникают.

2 голосов
/ 21 октября 2019

Возможно, вам нужно задать себе два вопроса:

  1. Что будет ftell() возвращаться, если fseek() не удалось?
  2. Могу ли я справиться со сбоем каким-либо значимым образом?

Если fseek() не удается, возвращается ненулевое значение. Если произойдет сбой ftell() (что, скорее всего, произойдет, если сбой fseek()), он вернет -1L, поэтому он более детерминирован, что с точки зрения обработки ошибок лучше.

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

Поскольку ваша цель - получить размер файла, а использование fseek / ftell - это просто способ синтеза этого,более разумно определить функцию размера файла, так что вызывающей стороне нужно заниматься только обработкой сбоя для получения действительного размера файла, а не сбоя деталей реализации. Суть в том, что если вам нужен размер файла, вам не нужно обрабатывать ошибки для fseek(), так как это было средством для достижения цели и не имеет прямого отношения к тому, что вам нужно достичь - сбой fseek() - этонедетерминированный побочный эффект, и эффект имеет неизвестный размер файла - лучше, чем вести себя «как если бы» ftell() не удалось, не рискуя ввести в заблуждение, фактически вызвав ftell():

long fsize( FILE* file )
{
    long size = -1 '  // as-if ftell() had failed
    if( fseek( file, 0, SEEK_END ) == 0 )
    {
        size = ftell( file ) ;
    }

    return size ;
}

Тогда ваш код будет:

const long file_size = fsize(my_file);

Тогда на уровне приложения вам нужно только обработать ошибку file_size < 0, вас не интересует, произошел ли сбой fseek() или ftell(), просто выне знаю размер файла.

1 голос
/ 21 октября 2019

fseek может вернуть ошибку в случае, когда дескриптор файла представляет собой канал (или последовательный поток).
В этот момент ftell даже не может сказать вам, где он находится, потому что в техобстоятельства более «куда бы вы ни пошли, вот и вы» .

0 голосов
/ 22 октября 2019

Да, проверьте возвращаемое значение, но будьте более осторожны с изменениями типа.

Обратите внимание, что диапазон size_t может быть больше или меньше 0...LONG_MAX.

// function returns an error flag
int fsize (FILE * file, size_t *size) {
  if (fseek(file, 0, SEEK_END)) {
    return 1; // fseek error  
  }

  long ftell_retval = ftell(file);
  if (ftell_retval == -1) {
    return 1; // ftell error
  }

  // Test if the file size fits in a `size_t`.
  // Improved type conversions here.
  // Portably *no* overflow possible.
  if (ftell_retval < 0 || (unsigned long) ftell_retval > SIZE_MAX) {
    return 1; // range error
  }

  *size = (size_t) ftell_retval;
  return 0;
}

Переносимость

Прямое преобразование long в size_t и наоборот является трудным делом, учитывая, что соотношение LONG_MAX, SIZE_MAX не определено. Это может быть <,==, >.

Вместо этого сначала проверьте < 0, затем, если оно положительное, преобразуйте в unsigned long. C указывает, что LONG_MAX <= ULONG_MAX, так что мы в порядке здесь. Затем сравните unsigned long с SIZE_MAX. Поскольку оба типа относятся к типу без знака, сравнение просто преобразуется в более широкий из двух. Опять нет потери дальности.

...