fseek/ftell是否有可能给出错误的文件大小?

5
在C或C++中,可以使用以下代码返回文件大小:
const unsigned long long at_beg = (unsigned long long) ftell(filePtr);
fseek(filePtr, 0, SEEK_END);
const unsigned long long at_end = (unsigned long long) ftell(filePtr);
const unsigned long long length_in_bytes = at_end - at_beg;
fprintf(stdout, "file size: %llu\n", length_in_bytes);

是否存在开发环境、编译器或操作系统,根据填充或其他特定情况下的信息,会从此代码返回错误的文件大小?在C或C++规范中是否有关于1999年左右的更改,导致此代码在某些情况下不再起作用?

对于这个问题,请假设我正在使用编译选项-D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE=1添加大文件支持。谢谢。


2
祝你好运,像那样打开“/proc/cpuinfo” :-) 换句话说,“不是每个文件都是文件”。(除非你为贝尔实验室工作。) - Kerrek SB
更传统的文件或输入流怎么样? - Alex Reynolds
有些情况下,文件大小绝对不适合于 long 类型。 - Bo Persson
我很高兴使用stat,但我对特定问题的答案很好奇。我在网上看到fseek/ftell被发布为查找文件大小的答案,我想知道这种方法的局限性在哪里,以便我可以做出明智的决定,使代码尽可能地可移植(这可能涉及到stat)。谢谢! - Alex Reynolds
现在,如果您试图找到这个问题的解决方案,并且fseek/ftell被列为其中的首要响应之一,就像我展示的那样,那么您会想知道有哪些捉住了(也许通过询问像Stack Overflow这样的编程论坛)吗?还是您会盲目地使用这段代码? - Alex Reynolds
显示剩余2条评论
4个回答

7
它无法在像/proc/cpuinfo/dev/stdin/dev/tty这样的不可寻址文件上工作,也无法在使用popen获取的管道文件上工作。如果该文件同时被另一个进程写入,它也无法工作。使用Posix stat函数可能更有效和更可靠。当然,在非Posix系统上可能无法使用此功能。

1
“stat”确实是更直接的方法,但它似乎在你提到的情况下失败了。在这些情况下,没有比读取到结尾更好的方法(如果有的话)。 - ugoren

4
< p > fseekftell 函数都由 ISO C 语言标准定义。

以下内容来自于2011年C标准的最新公开草案,但1990年、1999年和2011年的ISO C标准在这个领域都非常相似,甚至可以说是相同的。

7.21.9.4:

ftell 函数获取指向 stream 的流的文件位置指示器的当前值。对于二进制流,该值是从文件开头开始计算的字符数。对于文本流,其文件位置指示器包含未指定的信息,可由 fseek 函数使用,以将流的文件位置指示器返回到 ftell 调用时的位置;两个这样的返回值之间的差异不一定是写入或读取的字符数的有意义度量。

7.21.9.2:


fseek函数为指向stream的流设置文件位置指示器。如果发生读取或写入错误,则设置流的错误指示器并且fseek失败。 对于二进制流,新位置是从文件开始以字符为单位测量,通过将offset添加到whence指定的位置得到。如果whenceSEEK_SET,则指定的位置是文件开头;如果whenceSEEK_CUR,则指定的位置是文件位置指示器的当前值;如果whenceSEEK_END,则指定的位置是文件结尾。二进制流不需要有意义地支持whence值为SEEK_ENDfseek调用。 对于文本流,offset应为零,或者offset应为与同一文件关联的流上一个成功调用ftell函数返回的值,并且whence应为SEEK_SET
任何违反“应该”条款的行为都会使程序的行为未定义。
因此,如果文件以二进制模式打开,则ftell会给出从文件开头到当前位置的字符数,但是相对于文件末尾(SEEK_END)进行的fseek不一定有意义。这适用于将二进制文件存储在整个块中并且不跟踪写入最终块的数量的系统。
如果文件以文本模式打开,则可以使用0的偏移量将其定位到文件的开头或结尾,或者可以将其定位到先前调用ftell给出的位置;使用其他参数的fseek具有未定义的行为。这适用于从文本文件中读取的字符数不一定对应于文件中的字节数的系统。例如,在Windows上,读取CR-LF对("\r\n")仅读取一个字符,但在文件中前进2个字节。
在Unix-like系统上,文本模式和二进制模式的行为是相同的,fseek/ftell方法将起作用。我怀疑它也会在Windows上工作(我的猜测是ftell将给出字节偏移量,这可能与您在文本模式下调用getchar()的次数不同)。
请注意,ftell()返回类型为long的结果。在long为32位的系统上,此方法无法处理2 GiB或更大的文件。
您最好使用一些特定于系统的方法来获取文件的大小。由于fseek/ftell方法本身就是特定于系统的,例如Unix-like系统上的stat()。
另一方面,在大多数您可能遇到的系统上,fseek和ftell很可能会按您的预期工作。我相信有些系统它们不会起作用;抱歉,但我没有具体信息。
如果在Linux和Windows上工作已经足够好,并且您不关心大文件,那么fseek/ftell方法可能是可以的。否则,您应该考虑使用特定于系统的方法来确定文件的大小。
请记住,任何告诉您文件大小的东西只能告诉您此时此刻的大小。在访问之前,文件的大小可能会发生变化。

2

1) 表面上看,你的代码“还行”——我没发现任何问题。

2) 不,没有任何会影响fseek的“C或C++规范”。有一个Posix规范:

3) 如果你想要“文件大小”,我的第一选择可能会是“stat()”。这是Posix规范:

4) 如果你的方法出现了“问题”,那么我的第一个猜测可能是“大文件支持”。

例如,许多操作系统都有并行的“fseek()”和“fseek64()”API。

希望能有所帮助.. PSM


2
我相信<stdio.h>的一些函数,包括fopenfseekftell,可能是C99语言标准的一部分。 - Basile Starynkevitch
@Basile Starynkevitch:您说得完全正确。谢谢您:http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf - paulsm4

1

POSIX定义了fseek的返回值为“从文件开头开始的字节数”。假设这是一个新打开的文件,您的at_beg将始终为零。

因此,假设:

  1. 文件可寻址
  2. 没有并发问题需要考虑
  3. 文件大小可以用fseek/ftell变量所选的数据类型表示

那么您的代码应该在任何符合POSIX标准的系统上运行。


它将在Linux中错误地报告为普通文件的“/ proc”中的“files”上失败,但在所有其他情况下,它将如预期般工作。在许多其他情况下,例如块设备(硬盘驱动器),使用“stat”会失败。 - R.. GitHub STOP HELPING ICE

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接