计算文件长度时是否应该检查 fseek() 的 return 值?

Should I check the return value of fseek() when calculating the length of a file?

我有这个获取二进制文件长度的惯用代码片段:

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

…我知道,迂腐 fseek(file, 0, SEEK_END) 对二进制流有未定义的行为 [1] – 但坦率地说,在平台上这是一个我也没有的问题 fstat() 无论如何这是另一个问题的主题......

我的问题是:在这种情况下,我是否应该检查 fseek() 的 return 值?

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

        return 1;

    }

    const size_t file_size = ftell(my_file);

我从未见过 fseek() 在这种情况下被检查过,我也想知道 fseek() 可能会 return 什么样的错误。

编辑:

阅读后,我还认为在计算文件大小时处理fseek()ftell()return值的最佳方法是写一个专门的功能。然而 Clifford 的好建议无法处理 size_t 数据类型(毕竟我们需要 a size!),所以我想最终最实用的方法是使用一个指针来存储文件的大小,并仅在失败时保留我们专用函数的 return 值。这是我对 Clifford 的安全尺寸计算器解决方案的贡献:

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;

}

测试函数的 return 值并按时处理它始终是一个好习惯,否则可能会出现奇怪的行为,如果不进行详尽的调试,您将无法理解或发现这些行为。

您可以阅读以下link关于fseek的return值:fseekreturn值 部分。

if 语句在代码管道中可以忽略,但可以在出现问题时更轻松地处理问题。

fseek 可以 return 在文件句柄是管道(或串行流)的情况下出错。
到那时,ftell 甚至无法告诉你它在哪里,因为在那种情况下它更像是 "wherever you go, there you are".

你或许需要问自己两个问题:

  1. 如果 fseek() 失败了,ftell() return 会怎样?
  2. 我能以任何有意义的方式处理失败吗?

如果 fseek() 失败,它 return 是一个 非零 值。如果 ftell() 失败(如果 fseek() 失败,它可能会失败),它将 return -1L - 所以更具确定性,从错误处理的角度来看更好.

然而,fseek() 可能会失败但不会导致 ftell() 失败的潜在方式(也许不太可能,但失败模式是实现定义的),因此最好进行测试fseek() 以确保您没有从 ftell() 那里得到错误的答案。

由于您的目标是获取文件大小,而使用 fseek/ftell 只是一种综合方式,因此定义文件大小函数更有意义,这样调用者只需要关心处理获取有效文件大小的失败而不是实现细节的失败。关键是如果你想要文件大小,你不想处理 fseek() 的错误,因为这是达到目的的一种手段,与你需要实现的目标没有直接关系 - [= 的失败13=] 是一个不确定的副作用,其影响是一个未知的文件大小——比表现更好 "as-if" 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()是失败了没有兴趣,只是你不知道文件大小。

是的,检查 return 值,但在类型更改时要更加小心。

请注意 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_MAXSIZE_MAX 的关系未定义,将 long 直接转换为 size_t 反之亦然。可能是 <,==, >.

而是首先测试 < 0,然后,如果为正,则转换为 unsigned long。 C指定了LONG_MAX <= ULONG_MAX,所以我们这里就OK了。然后比较 unsigned longSIZE_MAX。由于这两种类型都是一些无符号类型,比较简单地转换为两者中更宽的一个。再次没有射程损失。