我理解ftell()和fseek()在C中的工作原理,但对于这个问题,我在任何地方都找不到任何精确的答案,包括最近的StackOverflow(LINK)。
那么你能回答以下问题吗?
- 是否可以得出结论,fgetpos()和fsetpos()只对以文本模式打开的文本文件相关,而对以二进制模式打开的文件无关?
- 什么样的位置信息是由fgetpos()填充的fpos_t对象,假设它不是一个长整数偏移量等,如ftell()给出的?站点cplusplusreference只告诉以下内容:
函数用流的位置指示符所需的信息填充pos指向的fpos_t对象,以将流恢复到当前位置
fgetpos()
和fsetpos()
对文本模式和二进制模式都是相关的。
fgetpos()
的优点是它保留了流中的完整位置,包括其内部的状态,因此您可以稍后恢复它。无论您是否处于文本模式,这都有效。如果您正在使用面向宽的流,或者在同一个文件中混合使用fgetc()
和fgetwc()
,这一点尤其重要,因为某些语言环境使用依赖于状态的多字节编码(状态取决于先前的读取)。
fseek()
和ftell()
也可以使用文本和二进制模式。然而,在文本模式中有一个重要的限制:您应该只使用带有0或ftell()
先前返回的值的fseek()
(在二进制模式中,您可以使用任何您想要的值)。这是因为文本模式读取可以改变读取返回的字节数与文件中有效的字节数(典型的例子是,windows文件中的2 CR+LF字节被转换为符号LF字节)。
由于ftell()
只返回long int
偏移量,如果需要的话,它无法跟踪多字节状态。所以使用fseek()
可能会失去这个状态。
不完全是。线索可以从Beej找到:
在几乎每一个系统上(当然是我所知道的每一个系统),人们不使用这些函数,而是使用ftell()和fseek()。这些功能的存在是为了防止您的系统无法记住文件作为简单字节偏移量的位置。
和Linux手册页:
在一些非unix系统上,fpos_t对象可能是一个复杂的对象这些例程可能是可移植地重新定位文本的唯一方法流。
在Windows上:
它假设缓冲区中的任何n字符最初都是rn中读取数据时已规范化的序列缓冲区。
也就是说,非(Windows-linebreak)文本文件在Windows中以文本模式打开时会出错,因为fsetpos
假设该文件确实是(Windows-linebreak)文本文件,因此不能包含没有r
的n
。
C11标准规定(我强调):
7.21.2/6:
每个面向宽的流都有一个相关联的mbstate_t对象存储流的当前解析状态。成功呼叫Fgetpos存储这个mbstate_t对象的值的表示形式作为fpos_t对象值的一部分。稍后成功呼叫到使用相同的存储fpos_t值的Fsetpos恢复关联的mbstate_t对象以及在控制流。
注意fseek
和ftell
对mbstate_t
对象没有什么可说的:它们不报告或还原它。因此,在面向宽的流(也就是说,你使用了面向宽的I/O函数的流)上,它们只重置文件位置,而不是(如果实现实际上有多个mbstate_t
对象的可能值)流的整个状态。
面向宽的流与文本流不是一回事,只是读取宽文本文件是它们的常见用途。实际上,fseek
和ftell
被记录为能够重置文本文件上的文件位置,只要您正确使用它们。所以我相信(我可能错了)fsetpos
和fgetpos
只需要在流上使用宽I/O函数。
除了在其他答案中提到的原因之外,如果您正在处理非常大的文件(包含超过LONG_MAX
字节的文件),则可能有必要使用fgetpos
和fsetpos
。对于LONG_MAX
= 231 & -的系统,这是一个真正的问题;1;现在超过20亿字节的文件并不少见。
如果您在一个实现POSIX.1-2001的系统上,有一个更好的选择,那就是在包含任何系统头文件之前使用#define _FILE_OFFSET_BITS 64
,然后使用fseeko
和ftello
。这些就像fseek
和ftell
一样,除了它们接受/返回一个off_t
的量,如果你做了上面的#define
,保证是一个整数类型,可以表示263 &减去;1,这对任何人来说都足够了。这个更好,因为你可以在off_t
上做算术;你不能用fpos_t
去你没有去过的地方。但是如果您不是在POSIX系统上,fgetpos
和fsetpos
可能是您唯一的选择。
(注意,有些系统会给你一个fpos_t
, 不能表示大于LONG_MAX
字节的文件偏移量。对于其中一些,应用相同的#define _FILE_OFFSET_BITS 64
设置将有所帮助。在其他情况下,如果你想要一个大文件,你就完全不走运了。