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

时间:2019-10-21 15:51:20

标签: c c99 fseek ftell c-standard-library

我有以下惯用代码段,用于获取二进制文件的长度:

    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()可能会返回哪种错误。

编辑:

阅读Clifford's answer之后,我还认为在计算文件大小时处理fseek()ftell()返回值的最佳方法是编写一个专用函数。但是Clifford的好建议无法处理size_t数据类型(毕竟我们需要 size !),所以我想最后最实用的方法是使用指针用于存储文件的大小,并仅在失败时保留我们专用函数的返回值。这是我对克利福德的安全尺寸计算器解决方案的贡献:

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 个答案:

答案 0 :(得分:3)

始终良好的做法是测试一个函数的返回值并按时处理它,否则可能会发生奇怪的行为,如果不进行详尽的调试,您将无法理解或发现该行为。

您可以在返回值部分中阅读有关fseek返回值的以下链接:fseek

if语句在代码管道中可忽略不计,而在出现问题时更易于处理。

答案 1 :(得分:2)

您可能需要问自己两个问题:

  1. 如果ftell()失败了,fseek()将返回什么?
  2. 我可以以任何有意义的方式处理失败吗?

如果fseek()失败,它将返回非零值。如果ftell()失败(如果fseek()失败,则很可能会失败),它将返回-1L-确定性更好,从错误处理的角度来看,这更好。

然而,fseek()可能会失败,但不会导致ftell()失败(可能不太可能,但是失败模式是由实现定义的),因此最好测试{{ 1}},以确保您没有从fseek()得到错误答案。

由于您的目标是获取文件大小,并且使用ftell() / fseek只是一种综合方法,因此定义文件大小函数更有意义,这样调用方只需要处理无法获得有效文件大小的故障,而不必关注实现细节的故障。关键是,如果您想要文件大小,则不需要处理ftell的错误,因为这是达到目的的一种手段,并且与您需要实现的目标没有直接关系-{{ 1}}是不确定的副作用,其影响是未知的文件大小-最好表现为“就好像” 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失败不感兴趣,只是您不知道文件大小

答案 2 :(得分:1)

fseek在文件句柄是管道(或串行流)的情况下会返回错误。
此时,ftell甚至无法告诉您它的位置,因为在这种情况下,“ 无论您走到哪里,都在哪里”

答案 3 :(得分:0)

是的,请检查返回值,但在更改类型时要格外小心。

请注意,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 longSIZE_MAX进行比较。由于这两种类型都是无符号类型,因此比较仅转换为两者中的较大者。再次没有范围损失。