fgetpos()和fsetpos()只适用于文本模式吗?如果不是字节数,fpos_t对象填充的位置/偏移数据是什么?

3

我了解在C语言中ftell()和fseek()的工作原理,但是对于这个问题,我无法找到任何确切的答案,包括最接近的StackOverflow帖子(LINK)。

那么请回答以下问题:

  • 可以得出结论,fgetpos()和fsetpos()仅适用于以文本模式打开的文本文件,而不适用于以二进制模式打开的文件吗?
  • fgetpos()填充fpos_t对象的是什么类型的位置信息?它不像ftell()给出的长整型偏移量等。网站cplusplusreference只提供以下信息:

该函数使用流的位置指针填充由pos指向的fpos_t对象,以恢复流到其当前位置所需的信息

3个回答

4

fgetpos()fsetpos() 适用于文本和二进制模式。

fgetpos() 的优点在于它保留了流中的完整位置,包括其内部状态,因此您可以稍后恢复它。无论您是否处于文本模式下,这都是有效的。如果您同时使用定向宽字符流或在同一文件中混合使用fgetc()fgetwc(),则这尤为重要,因为某些区域设置使用状态依赖的多字节编码(状态取决于先前的读取)。

fseek()ftell() 也可用于文本和二进制模式。但是,在文本模式下有一个重要限制:您只应该使用fseek()与0或先前由ftell()返回的值(在二进制模式下,您可以使用任何值)。这是因为文本模式读取可能会改变与实际文件中读取的字节数相比返回的字节数(典型示例是Windows文件中的2个CR + LF字节,它们被转换为单个LF字节)。

由于ftell()仅返回long int偏移量,如果需要,它无法跟踪多字节状态。因此,使用fseek()可能会丢失此状态。


我理解了你的大部分回答。但是第一行对我来说是新的东西。你能否提供一个简短的可编译示例程序,在其中使用fgetpos()和fsetpos()二进制模式?否则,你能否提供一个链接让我可以阅读更多相关信息? - Meathead
1
@Meathead:如果你有一个使用文本模式的代码示例,那么你可以通过更改文件打开方式来创建一个使用二进制模式的示例。这两个函数被定义为无论文件以哪种模式打开,都能执行它们的功能。 - Steve Jessop
1
@SteveJessop 谢谢。我会做的。无论如何,你的确认对我来说就像自己测试一样好 :-) - Meathead
1
不能在同一个流上混合使用面向字节和面向宽字符的操作,否则会出现问题。除此之外,+1。 - rici

2
不完全正确。可以从Beej中找到线索:

在几乎所有系统上(我知道的每个系统肯定都是这样),人们不使用这些函数,而是使用ftell()和fseek()。这些函数存在的原因是为了防止您的系统无法将文件位置记忆为简单的字节偏移量。

并且在Linux man pages中:

在一些非UNIX系统上,fpos_t对象可能是一个复杂的对象,这些例程可能是可移植地重新定位文本流的唯一方法。

还有在Windows上:

它假设缓冲区中的任何\n字符最初都是\r\n序列,在读入缓冲区时已被规范化。

换行符不是 Windows 标准的文本文件会在 Windows 中以文本模式打开时出现问题,因为 fsetpos 假定该文件实际上是一个 Windows 标准的文本文件,因此不能包含没有 \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 对象)整个流的状态。
“Wide-oriented streams”并不等同于文本流,只是读取宽字符文本文件是它们的常见用途。实际上,“fseek”和“ftell”被记录为能够在文本文件上重置文件位置,只要你正确使用它们。因此,我认为(可能我错了),只有在流上使用宽I/O函数时才需要使用“fsetpos”和“fgetpos”。

感谢提供详细信息,尤其是回答了我问题的第二部分。但是,您能否解释一下另一个答案中提到的如何在二进制模式下使用fgetpos()和fsetpos()?我可以使用fgetpos()在二进制文件中查找位置指示器,并像在文本文件中一样使用fsetpos()中的信息吗?没有区别吗? - Meathead
@Meathead:没有区别。只是因为(在存在二进制和文本模式差异的系统上),你通常不会在二进制文件上使用宽字符 I/O 函数,所以使用 fgetpos 没有比使用 fseek 更好的效果。在 POSIX 系统上,文本和二进制模式被定义为相同的,你可能会使用面向宽字符的二进制文件,但是(如果我理解正确,这对我来说已经很久没关系了),POSIX 不允许有状态编码作为系统范围的字符模式,因此没有什么需要恢复的。 - Steve Jessop

2
除了其他答案中提到的原因外,如果您正在使用超过LONG_MAX字节的文件,即包含非常大的文件,则可能需要使用fgetposfsetpos。在LONG_MAX为2 31 -1的系统上,这是一个真正的问题;现在,具有超过20亿字节的文件并不罕见。
如果您正在使用实现POSIX.1-2001的系统,则有更好的选择,即在包含任何系统头文件之前定义_FILE_OFFSET_BITS 64,然后使用fseekoftello。这些函数与fseekftell类似,只是它们接受/返回一个off_t数量,只要您已经进行了上述#define,则保证是可以表示2 63 -1的整数类型,这应该足够了。这样做更好,因为您可以对off_t进行算术运算;您无法使用fpos_t去到您以前没有到达过的地方。但是,如果您不在POSIX系统上,则fgetposfsetpos可能是您唯一的选择。
(请注意,某些系统将为您提供无法表示大于LONG_MAX字节的文件偏移量的fpos_t。在其中一些系统上,应用相同的#define _FILE_OFFSET_BITS 64设置将有所帮助。在其他情况下,如果您想要一个巨大的文件,则完全没有运气可言。)

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