13

我已经阅读了展示如何使用 fseek 和 ftell 来确定文件大小的帖子。

FILE *fp;
long file_size;
char *buffer;

fp = fopen("foo.bin", "r");
if (NULL == fp) {
 /* Handle Error */
}

if (fseek(fp, 0 , SEEK_END) != 0) {
  /* Handle Error */
}

file_size = ftell(fp);
buffer = (char*)malloc(file_size);
if (NULL == buffer){
  /* handle error */
}

我正要使用这种技术,但后来我遇到了这个描述潜在漏洞的链接。

该链接建议改用 fstat 。任何人都可以对此发表评论吗?

4

5 回答 5

16

该链接是 CERT 提供的众多无意义的 C 编码建议之一。他们的理由是基于 C 标准允许实现采取的自由,但POSIX 不允许fstat这样做,因此在您有替代方案的所有情况下都无关紧要。

POSIX 要求:

  1. "b"修饰符 forfopen无效,即文本模式和二进制模式的行为相同。这意味着他们对在文本文件上调用 UB 的担忧是无稽之谈。

  2. 该文件具有由写入操作和截断操作设置的字节分辨率大小。这意味着他们对文件末尾随机数的空字节的担忧是无稽之谈。

可悲的是,他们发表了所有像这样的废话,很难知道应该认真对待哪些 CERT 出版物。真可惜,因为很多都是认真的。

于 2011-05-11T00:29:59.280 回答
7

如果您的目标是查找文件的大小,那么您绝对应该使用fstat()它或其朋友。这是一种更直接和更具表现力的方法——您实际上是在要求系统告诉您文件的统计信息,而不是更迂回的 fseek/ftell 方法。

额外提示:如果您只想知道文件是否可用,请使用access()而不是打开文件甚至统计它。这是一个更简单的操作,许多程序员都不知道。

于 2011-05-11T00:18:14.683 回答
4

The reason to not use fstat is that fstat is POSIX, but fopen, ftell and fseek are part of the C Standard.

There may be a system that implements the C Standard but not POSIX. On such a system fstat would not work at all.

于 2011-05-11T00:36:34.140 回答
4

我倾向于同意他们的基本结论,即您通常不应该在代码的主流中直接使用fseek/ftell代码——但您可能也不应该使用fstat。如果你想要一个文件的大小,你的大部分代码应该使用一个清晰​​、直接的名字,比如filesize.

现在,使用可用的地方和(例如)在 Windows(通常不可用的最明显平台)上实现它可能会更好。fstatFindFirstFilefstat

故事的另一面是fseek关于二进制文件的许多(大多数?)限制实际上起源于 CP/M,它没有在任何地方明确存储文件的大小。文本文件的结束由 control-Z 表示。然而,对于二进制文件,您真正知道的只是哪些扇区用于存储文件。在最后一个扇区中,您有一些经常(但不总是)填零的未使用数据。不幸的是,可能有重要的零和/或不重要的非零值。

如果整个 C 标准是在被批准之前编写的(例如,如果它是在 1988 年开始并在 1989 年完成的),他们可能会完全忽略 CP/M。然而,无论好坏,他们在大约 1982 年左右开始研究 C 标准,当时 CP/M 的使用范围仍然足够广泛,以至于它不能被忽视。到 CP/M 离开时,许多决定已经做出,我怀疑有人想重新审视它们。

然而,对于今天的大多数人来说,这是没有意义的——如果没有大量工作,大多数代码都不会移植到 CP/M;这是要处理的相对较小的问题之一。让现代程序在仅 48K(左右)的内存中运行代码和数据是一个严重的问题(对于大容量存储来说,最大大约 1 兆字节将是另一个严重的问题)。

不过,CERT 确实有一个好处:您可能不应该通常这样做)找到文件的大小,分配那么多空间,然后假设文件的内容适合那里。即使 fseek/ftell 可以为您提供现代系统的正确大小,但在您实际读取数据时,该数据可能已经过时,因此无论如何您都可能超出缓冲区。

于 2011-05-11T01:15:36.227 回答
2

根据C 标准,§7.21.3

将文件位置指示符设置为文件结尾,与 一样fseek(file, 0, SEEK_END),对于二进制流(因为可能的尾随空字符)或任何具有状态相关编码的流不能确定以初始移位状态结束,具有未定义的行为。

有法律依据的人可能会认为可以通过计算文件大小来避免这种 UB:

fseek(file, -1, SEEK_END);
size = ftell(file) + 1;

但是 C 标准也这样说:

二进制流不需要有意义地支持 wherece 值为 SEEK_END 的 fseek 调用。

因此,对于 fseek / SEEK_END,我们无法解决此问题。不过,我更喜欢 fseek / ftell 而不是特定于操作系统的 API 调用。

于 2012-11-07T11:59:19.120 回答