如何在没有fseek和ftell的情况下获得ANSI C中的文件大小?

时间:2012-03-22 21:25:40

标签: c file size filesize

在寻找找到FILE*文件大小的方法的同时,我遇到了this article建议反对它。相反,它似乎鼓励使用文件描述符和fstat

但是我的印象是fstatopen和文件描述符一般都不那么便携(经过一​​些搜索,我发现了一些东西effect

有没有办法在ANSI C中获取文件的大小,同时与文章中的警告保持一致?

7 个答案:

答案 0 :(得分:14)

在标准C中,fseek / ftell舞蹈几乎是城里唯一的游戏。你做的任何其他事情至少在某种程度上取决于你的程序运行的特定环境。不幸的是,舞蹈也存在问题,如你所链接的文章所述。

我猜你总是可以读取文件中的所有内容,直到EOF并继续跟踪 - 例如fread()

答案 1 :(得分:6)

文章声称fseek(stream, 0, SEEK_END)是未定义的行为,引用了上下文脚注

脚注出现在处理面向广播的流的文本中,这些流是对它们执行的第一个操作是对宽字符的操作。

这种未定义的行为源于两段的组合。首先§7.19.2/ 5说:

  

- 二进制面向广义的流具有归因于文本和二进制流的文件定位限制。

文本流文件定位的限制(§7.19.9.2/ 4)是:

  

对于文本流,offset应为零,或offset应为先前成功调用与同一文件关联的流上的ftell函数返回的值whence应为SEEK_SET

这使得面向广泛的流的<{1}}未定义的行为 。对于面向字节的流,没有像§7.19.2/ 5这样的规则。

此外,当标准说:

  

二进制流无需支持fseek(stream, 0, SEEK_END) fseek值为whence的{​​{1}}次呼叫。

这并不意味着它是未定义的行为。但如果流支持它,那没关系。

显然存在允许二进制文件具有粗粒度粒度,即大小为多个磁盘扇区而不是多个字节,因此允许在末尾神奇地出现未指定数量的零。二进制文件。在这种情况下,SEEK_END无法得到有意义的支持。其他示例包括管道或无限文件,如SEEK_END。但是,C标准无法区分这些情况,因此如果您想考虑这种情况,则会遇到与系统相关的调用。

答案 2 :(得分:3)

使用fstat - 需要文件描述符 - 可以从FILE*的{​​{3}}获取 - 因此,您可以掌握大小以及其他详细信息。

fstat(fileno(filePointer), &buf);

其中filePointerFILE *

buf

struct stat {
    dev_t     st_dev;     /* ID of device containing file */
    ino_t     st_ino;     /* inode number */
    mode_t    st_mode;    /* protection */
    nlink_t   st_nlink;   /* number of hard links */
    uid_t     st_uid;     /* user ID of owner */
    gid_t     st_gid;     /* group ID of owner */
    dev_t     st_rdev;    /* device ID (if special file) */
    off_t     st_size;    /* total size, in bytes */
    blksize_t st_blksize; /* blocksize for file system I/O */
    blkcnt_t  st_blocks;  /* number of 512B blocks allocated */
    time_t    st_atime;   /* time of last access */
    time_t    st_mtime;   /* time of last modification */
    time_t    st_ctime;   /* time of last status change */
};

答案 3 :(得分:2)

不同的操作系统为此提供了不同的api。例如,在Windows中我们有:

GetFileAttributes()

在MAC中我们有:

[[[NSFileManager defaultManager] attributesOfItemAtPath:someFilePath error:nil] fileSize];

但是原始方法只能通过fread和fseek: How can I get a file's size in C?

答案 4 :(得分:2)

您不能总是避免编写特定于平台的代码,尤其是当您必须处理作为平台功能的内容时。文件大小是文件系统的函数,因此作为规则我会使用本机文件系统API通过fseek / ftell舞蹈获取该信息。我会围绕它创建自己的通用包装器,以便不会使用特定于平台的详细信息污染应用程序逻辑,并使代码更容易移植。

答案 5 :(得分:2)

执行摘要是必须使用fseek / ftell,因为没有其他选择(甚至是特定于实现的)更好。

潜在的问题是&#34;尺寸&#34;以字节为单位的文件并不总是与文件中数据的长度相同,并且在某些情况下,数据的长度不可用。

POSIX示例是将数据写入设备时发生的情况;操作系统只知道设备的大小。一旦写入数据并且(FILE *)关闭,就没有写入数据长度的记录。如果打开设备进行读取,则fseek / ftell方法将失败或为您提供整个设备的大小。

当ANSI-C委员会坐在1980年代末时,成员记住的许多操作系统根本没有将数据长度存储在文件中;而是他们存储了文件的磁盘块,并假设数据中的某些东西终止了它。 &#39;文字&#39;流表示这一点。打开二进制文件&#39;这些文件上的流不仅显示了神奇的终结符字节,还显示了超出它的任何字节,这些字节从未写入但恰好位于同一个磁盘块中。

因此编写了C-90标准,以便 有效使用fseek技巧;结果是符合要求的程序,但结果可能不是您所期望的。该程序的行为不是“未定义的”#39;在C-90定义中并没有实现定义&#39; (因为在UN * X上它随文件而变化)。它也不是无效的&#39;。而是你得到一个你不能完全依赖的数字,或者,取决于fseek,-1和一个错误的参数。

在实践中,如果技巧成功,你会得到一个至少包含所有数据的数字,这可能就是你想要的,如果技巧失败,那几乎肯定是别人的错。

John Bowler

答案 6 :(得分:-2)

这篇文章有一点逻辑问题。

它(正确地)标识C函数的某些用法具有未由ISO C定义的行为。但是,为了避免这种未定义的行为,本文提出了一种解决方案:将该用法替换为特定于平台的函数。不幸的是,根据ISO C,使用特定于平台的函数也是未定义的。因此,该建议并未解决未定义行为的问题。

1999年标准副本中的引用证实了所谓的行为确实未定义:

  

二进制流不需要有意义地支持具有SEEK_END值的fseek调用。 [ISO 9899:1999 7.19.9.2第3段]

但未定义的行为并不意味着&#34;不良行为&#34 ;;它只是ISO C标准没有定义的行为。并非所有未定义的行为都是相同的。

某些未定义的行为是语言中可以提供有意义的扩展的区域。该平台通过定义行为来填补空白。

提供可以从fseek搜索的工作SEEK_END是代替未定义行为的扩展示例。可以确认给定平台是否支持来自fseek的{​​{1}},如果已经配置,则可以使用它。

提供像SEEK_END这样的单独函数也是代替未定义行为的扩展(调用不在ISO C中且未在C程序中定义的函数的未定义行为)。如果可以的话,可以使用它。

请注意,具有POSIX lseek等功能的平台也可能具有可从lseek起作用的ISO C fseek。另请注意,在二进制文件上的SEEK_END无法从fseek搜索的平台上,可能的原因是这是不可能的(无法提供API来执行此操作,这就是C库的原因函数SEEK_END无法支持它。)

因此,如果fseek确实在给定平台上提供了所需的行为,那么就不需要对程序进行任何操作;改变它以使用该平台的特殊功能是浪费精力。另一方面,如果fseek没有提供行为,那么无论如何都可能没有。

请注意,即使包含不在程序中的非标准标头也是未定义的行为。 (通过省略行为的定义。)例如,如果以下内容出现在C程序中:

fseek

之后没有定义行为。 [请参阅下面的参考资料。] 当然,已定义预处理指令#include <unistd.h> 的行为。但这会产生两种可能性:标题#include不存在,在这种情况下需要诊断。或者标题确实存在。但在这种情况下,内容是未知的(就ISO而言;没有为库记录这样的标题)。在这种情况下,include伪指令会引入一段未知的代码,并将其合并到翻译单元中。无法定义未知代码块的行为。

<unistd.h>是用于在给定平台上执行任何操作的语言中的转义符号之一。

以点的形式:

  1. 未定义的行为本身并不是“坏”&#34;并且本质上不是安全漏洞(当然它可以是!例如缓冲区溢出链接到指针算术和解除引用区域中的未定义行为。)
  2. 仅为了避免未定义的行为而将一个未定义的行为替换为另一个,这是毫无意义的。
  3. 未定义的行为只是ISO C中使用的一个特殊术语,用于表示超出ISO C定义范围的内容。它并不意味着没有被世界上任何人定义&#34;并没有暗示某些东西是有缺陷的。
  4. 依赖一些未定义的行为对于制作大多数真实世界的有用程序是必要的,因为许多扩展是通过未定义的行为提供的,包括特定于平台的头和函数。
  5. 未定义的行为可以通过ISO C外部行为的定义来取代。例如,POSIX.1(IEEE 1003.1)系列标准定义了包含#include <platform-specific-header.h>的行为。未定义的ISO C程序可以是定义良好的POSIX C程序。
  6. C中的某些问题无法解决,而不依赖于某种未定义的行为。一个例子是一个程序,它希望从文件末尾向后搜索这么多字节。
  7. 参考文献: