fseek(f, 0, SEEK_END);
size = ftell(f);
如果 ftell(f) 告诉我们当前文件位置,那么这里的大小应该是文件末尾到开头的偏移量。为什么大小不是 ftell(f)+1?ftell(f) 不应该只给我们文件末尾的位置吗?
文件位置就像文本输入小部件中的光标:它们位于文件的字节之间。如果我画一张图,这可能最容易理解:
这是一个假设文件。它包含四个字符:a、b、c和d。每个字符都有一个自己的小盒子,我们称之为“字节”。(这个文件是 ASCII。)第五个框被划掉了,因为它还不是文件的一部分,但是如果你在文件中附加了第五个字符,它就会出现。
此文件中的有效文件位置为 0、1、2、3 和 4。其中有 5 个,而不是 4 个;它们对应于框之前、之后和之间的垂直线。当您打开文件时(假设您不使用"a"
),您从位置 0 开始,即文件中第一个字节之前的行。当您搜索到最后时,您到达位置 4,即文件中最后一个字节之后的行。因为我们从零开始计数,所以这也是文件中的字节数。(这是我们从零开始计数的几个原因之一,而不是从一开始。)
我不得不警告你,有几个原因
fseek(fp, 0, SEEK_END);
long int nbytes = ftell(fp);
可能不会给你你真正想要的数字,这取决于你所说的“文件大小”和文件的内容。没有特别的顺序:
在 Windows 上,如果您以文本模式打开文件,则从ftell
该文件中获得的数字不是文件开头的字节偏移量;它们更像fgetpos
cookie,只能在后续调用fseek
. 如果您需要在 Windows 上查找文本文件,您最好以二进制模式打开文件并自己处理 DOS 和 Unix 行尾——这实际上是我对生产代码的一般建议,因为它完全有可能在 Unix 系统上有一个以 DOS 行结尾的文件,反之亦然。
在long int
32 位的系统上,文件很容易比那个大,在这种情况下ftell
会失败,返回 -1 并设置errno
为EOVERFLOW
. 符合 POSIX.1-2001 的系统提供了一个名为的函数,该函数ftello
返回一个off_t
可以表示较大文件大小的数量,前提是您将其放在#define _FILE_OFFSET_BITS 64
所有源文件的最顶部(在任何#include
s 之前)。我不知道 Windows 等效项是什么。
如果您的文件包含超出 ASCII 的字符,则文件中的字节数很可能与文件中的字符数不同。(例如,如果文件以 UTF-8 编码,则字符啡</kbd> will take up three bytes, Ä will take up either two or three bytes depending on whether it's "composed", and జ్ఞా will take up twelve bytes because, despite being a single grapheme, it's a string of four Unicode code points.) ftell(o)
will still tell you the correct number to pass to malloc
, if your goal is to read the entire file into memory, but iterating over "characters" will not be so simple as for (i = 0; i < len; i++)
.
如果您使用 C 的“宽流”和“宽字符”,那么就像 Windows 上的文本流一样,您从ftell
该文件中获得的数字不是字节偏移量,除了后续调用fseek
. 但是宽流和字符无论如何都是一个糟糕的设计。如果您坚持在窄流和字符中手动处理 UTF-8,您实际上更有可能正确处理世界上所有的语言。
我不确定为什么fseek()
/ftell()
被教导为获取文件大小的通用方法。它之所以有效,是因为实现定义它可以工作。POSIX 确实如此。Windows 也适用于二进制流,但不适用于文本流。
不对“这是获取文件中字节数的方式”添加警告或警告是错误的。因为当程序员第一次进入一个没有将fseek()
/定义ftell()
为字节偏移的系统时,他们就会遇到问题。我已经看到了它。
“但有人告诉我,你总是可以这样做。”
“嗯,不。教你的人是错的。”
因为在严格符合 C 代码中不可能使用fseek()
/ftell()
来获取文件的大小。
对于二进制流,7.21.9.2 The fseek
function,第 3 段C 标准:
对于二进制流,新位置(以文件开头的字符为单位)通过添加
offset
到指定的位置来获得whence
。如果是 ,则指定位置是文件的开头,如果是SEEK_CUR ,则为文件位置指示符的当前值, 如果whence
是,则为文件结尾。二进制流不需要有意义地支持值为 的 调用。SEEK_SET
SEEK_END
fseek
whence
SEEK_END
脚注 268 特别指出:
将文件位置指示符设置为文件结尾,与 一样
fseek(file, 0, SEEK_END)
, 对于二进制流(因为可能出现尾随空字符)或任何具有状态相关编码但不能确保在初始移位状态结束的流具有未定义的行为。
所以你不能寻找二进制流的结尾来获取文件的字节大小。
对于文本流,7.21.9.4 The ftell
function,第 2 段指出:
该
ftell
函数获取指向的流的文件位置指示符的当前值stream
。对于二进制流,该值是从文件开头开始的字符数。 对于文本流,其文件位置指示符包含未指定的信息,fseek
函数可用于将流的文件位置指示符返回到ftell
调用时的位置; 两个这样的返回值之间的差异不一定是衡量写入或读取字符数的有意义的指标。
因此,您不能ftell()
在文本流上使用来获取字节数。
我知道获取文件中字节数的唯一严格一致的方法是一个接一个地读取它们fgetc()
并计算它们。