【问题标题】:fseek/fsetpos may discard stream buffer?fseek/fsetpos 可能会丢弃流缓冲区?
【发布时间】:2018-07-22 20:43:33
【问题描述】:

在 fopen 的 C 标准中,关于以更新模式打开的文件 (C11 7.21.5.3/7),输出后跟输入需要对 fflush 或文件定位函数(fseek、fsetpos 或 rewind)的干预调用。但是,不需要任何文件定位函数来对输出缓冲区执行任何操作。

POSIX 标准对fopen 和更新模式保持相同的要求。与 C 标准一样,fsetpos 不需要对输出缓冲区进行任何操作。但是,将缓冲区写入文件需要fseek

在 C 和 POSIX 的情况下,当 fsetpos 被调用时,符合标准的实现似乎可以随意丢弃写缓冲区,而 C 似乎允许 fseek 做同样的事情。我的第一个问题是我是否遗漏了标准中的相关内容。这意味着可移植应用程序必须调用 fflush(或在 POSIX 的情况下为 fseek/rewind)以确保在从输出切换到输入之前实际写入缓冲输出。

显然,丢弃写缓冲区违背了所有写函数的意图,我不知道有任何实现这样做或任何类似违反直觉的事情。我也知道我的意识有限,所以我的第二个问题是是否有任何符合要求的实现不能确保缓冲的内容最终被写入正确的位置。

就上下文而言,GNU 文档对fopen 和更新模式保持相同的要求。与 C 和 POSIX 一样,fsetpos 没有说明输出缓冲区,但我的测试表明我的版本确实刷新了缓冲区。但是,fseek 可能会刷新缓冲区或记住足够的信息以确保最终正确写入其内容。

TL;DR: C 或 POSIX 是否不允许 fsetpos 丢弃写入缓冲区?是否有执行此操作的实现?

编辑:目前还没有人提供可靠的证据表明任一标准都禁止 fsetpos 丢弃写入缓冲区。同样,没有人提到任何执行此操作的实现。但是,这在 C 标准(附件 J)的可移植性问题列表中没有提到,这表明这是一个疏忽,而不是一个模糊的可移植性问题。此外,正如 R.. 所提到的,没有禁止完全不相关的函数丢弃缓冲区。

【问题讨论】:

    标签: c posix


    【解决方案1】:

    我不明白你从哪里得到这个想法。 POSIX 比 C 标准更详细地介绍了缓冲行为,因为它必须处理 stdio FILE 流与访问相同文件的其他方式的交互。但是 C 标准中没有任何内容表明当您调用 fsetpos 时允许实现丢失输出。从逻辑上讲,数据已经写入。

    此外,fsetpos 的规范(C11 7.21.9.3,¶2)如下:

    如果发生读取或写入错误,则设置流的错误指示器并且 fsetpos 失败。

    可能发生写入错误的唯一合理原因是某种写入操作,而唯一合理的写入操作是刷新挂起的输出。

    【讨论】:

    • C 标准中似乎没有任何内容禁止符合标准的 fsetpos 实现丢弃写入缓冲区、查找并返回成功。存在有关写入错误的文本并不意味着需要写入。如果实现选择写入缓冲区,则在遇到写入错误时它必须遵循引用的文本。我同意大多数实现都努力确保缓冲区中的数据被写入,但这不是问题。
    • @ov2k:写的要求在fputc函数的规范中。从逻辑上讲,写入发生在那个时候。指定缓冲,以便以后可以进行底层写入,但不允许将其静默丢弃。 像往常一样 C 标准在拼写上并不严格,但这就是 C 标准中的一切。委员会不认为这是一种有效地利用时间来纠正缺乏严格性的问题,除非其意图并不明显,而这里是完全显而易见的。
    • @ov2k:我认为您在 POSIX 中看到的内容很容易解释为 fseek 被赋予了涵盖与文件描述符交互的大型 CX 描述,而 fsetpos 大多只是反映 C文本。但是 POSIX 遵循 C,并且不能强制实现不符合 C 要求。此外,我认为您错过了fsetpos 的文本:如果 [CX> 流未缓冲 或需要刷新流的缓冲区,则 fsetpos() 函数将失败,并且对 fsetpos() 的调用会导致调用底层 lseek() 或 write(),并且:...
    • 就像 fsetpos 的 C 文本一样,POSIX 标准的那部分只指定了如果实现选择写入缓冲区应该发生什么。这两个标准都没有要求fsetpos 写入缓冲区,而且两个标准似乎都没有禁止它丢弃缓冲区。 POSIX 的fsetposfseek 之间的差异背后的意图还不清楚。
    • @ov2k:不,它没有说“如果实现选择写入缓冲区”。它说“将失败如果...流的缓冲区需要刷新。”如果实现能够在不刷新缓冲区的情况下满足所有其他要求(例如,如果缓冲区绑定到文件中的特定位置并且可以留待稍后,在查找之后刷新),那么它们“不需要”冲到这里。但如果实现无法做到这一点,它们“需要被刷新”,如果不能做到这一点,它将失败
    【解决方案2】:

    fsetpos 案例中,除了 Errors 部分(两次)中的此注释之外,我没有看到任何需要刷新的内容:

    或者需要刷新的缓冲区,

    这看起来像是 POSIX 中的一个遗漏。请在Austin Group issue tracker 中提出澄清请求。

    【讨论】:

    • 我不完全清楚你没有看到任何需要冲洗的东西是什么意思,如果我误解了,我深表歉意。如果您的意思是在 fseek 中看不到 POSIX 需要它的位置,那么它会声明:“如果流是可写的并且缓冲的数据尚未写入基础文件,fseek() 将导致未写入的数据写入文件。”没有关于 fsetpos 的相应文本。
    • 如果您的意思是您不理解可移植应用程序必须刷新的含义,那么我将尝试澄清。如果允许符合要求的实现在查找时丢弃写入缓冲区,则假定 fsetpos 将写入缓冲区的应用程序将在此平台上丢失一些输出。
    • 无论如何,我不确定为什么 fseek 需要刷新而 fsetpos 不需要。如果我在这里没有得到任何好的答案,请向奥斯汀集团寻求澄清。
    • 对不起,我不得不提一下我的评论是关于fsetpos,而不是fseek
    【解决方案3】:

    C 标准似乎没有明确禁止fsetpos(或任何其他函数)丢弃缓冲区,这似乎是一个迂腐的缺陷。但是,C99 基本原理文档 (7.19.5.3) 声明 fsetposfseekrewindfflush“确保 I/O 缓冲区已被刷新”。尚不清楚为什么此类文本未包含在标准中,尽管人们可以推测 GNU 和回写式缓存以及是否需要在查找操作上强制磁盘 I/O。

    在实践中,这意味着人们应该能够假设写入、搜索、然后读取将返回预期的数据。但是,鉴于至少一种实现 (GNU) 在搜索时可能并不总是刷新,因此不应假设数据会在没有明确刷新请求的情况下到达内核(更不用说底层设备)了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多