【问题标题】:Is opening the SAME file in two different fstreams Undefined Behaviour?是否在两个不同的 fstream 未定义行为中打开相同的文件?
【发布时间】:2020-01-04 08:34:17
【问题描述】:

这个recently asked question 提出了另一个有趣的问题,正如comments to one of its answers 中所讨论的那样。

总结一下:当随后尝试“同时”从两个流读取/向两个流写入数据时,OP 存在如下代码问题:

ifstream infile;
infile.open("accounts.txt");

ofstream outfile;
outfile.open("accounts.txt");

虽然问题本身已成功解决,但它提出了一个我无法找到权威答案的问题(我已经对 Stack Overflow 和更广泛的网络进行了一些相当广泛的搜索)。

非常清楚地说明了在调用已经与 文件 (cppreference) 关联的 open() 方法时会发生什么,但我当file 已经与(不同的)stream 相关联时(在本例中)会发生什么,无法找到答案。

如果流已经与文件相关联(即,它已经 open),调用该函数失败。

我可以在这里看到几种可能的情况:

  1. 第二次打开的调用将失败,任何尝试写入它的操作也将失败(但在引用的问题中不是)。
  2. 第二个打开的调用将“覆盖”第一个,有效地关闭它(这可以解释上述代码中遇到的问题)。
  3. 两个流都保持打开状态,但进入关于其内部文件指针和缓冲区的“相互破坏”匹配。
  4. 我们进入了未定义(或实现定义)行为的领域。

请注意,由于第一个 open() 调用是由输入流进行的,因此操作系统不一定会像对输出流那样“锁定”文件。 p>

那么,有人对此有明确的答案吗?或者来自标准的引用(如果找不到更权威的,cppreference 将是“可接受的”)?

【问题讨论】:

  • “cppreference 就足够了”?根据我的经验,这种情况很少见。标准中的微妙细节通常在该网站上找不到。
  • @paxdiablo 说得好 - 请参阅(次要)编辑!
  • 有趣的是,打开的流包含文本“好像通过调用 fopen”,因此请遵循相关的 C 标准。不确定这有多大帮助,因为粗略地看一眼 C11 也没有表明如果您使用两个不同的 FILE* 变量打开同一个文件应该发生什么定义的行为:-)
  • C 标准中什么也没说。在 posix 兼容的操作系统 (Linux) 上,您不会有任何问题,fopen 将调用open 系统调用,这将为每个流创建一个open file description。一切都会按预期进行。
  • 嗯,它应该按照常识工作。首先,打开模式应该是适当的(非独占,不截断),然后不能处理文件的公共部分。例如,如果outfile 在末尾附加内容,infile 读取预先存在的内容,它应该可以正常工作。但是,很难预测何时读取会到达文件末尾...

标签: c++ fstream undefined-behavior


【解决方案1】:

basic_filebuf::open(以及所有依赖它的东西,比如fstream::open)没有说明在这种情况下会发生什么。文件系统可能允许,也可能不允许。

标准的意思是,如果文件成功打开,那么你就可以按照界面来玩了。如果它没有成功打开,那么就会出现错误。也就是说,该标准允许文件系统允许或禁止它,但它没有说明必须发生什么。该实现甚至可以随机禁止它。或者禁止您以任何方式打开任何文件。所有(理论上)都是有效的。

【讨论】:

  • 该标准不要求实现反映底层操作系统的行为,而是将此类事情视为超出其管辖范围。例如,如果操作系统可能允许使用两个不同的路径打开文件两次以进行追加,其语义是给操作系统的所有内容都将按顺序写入文件,则针对该操作系统的实现可能会直接传递所有输出到操作系统,或者可能向每个流添加自己的缓冲,或者实现可能会在每次写入之前查询文件位置,并在发现文件发生更改时以任何它认为合适的方式行事。
【解决方案2】:

对我来说,这甚至超出了“实现定义”字段。根据底层文件系统或操作系统(某些操作系统禁止打开文件两次),相同的代码会有不同的行为。

【讨论】:

    【解决方案3】:

    没有。

    标准没有讨论这种情况。

    它甚至不受实现(您的编译器、标准库实现等)的管理。

    流最终要求操作系统以所需的模式访问该文件,由操作系统决定当时是否应授予该访问权限。

    一个简单的类比是您的程序通过网络对 Web 应用程序进行一些 API 调用。也许 Web 应用程序不允许每分钟调用超过 10 次,并且如果您尝试超过此次数,则会返回一些错误代码。但这并不意味着您的程序在这种情况下具有未定义的行为。

    【讨论】:

    • 即使操作系统定义了语义,实现也可以注入它自己认为合适的语义。例如,它可以添加逻辑,以便如果它看到为追加打开了两次相同的路径,则两者都将使用相同的缓冲区集,或者如果操作系统会以其他方式运行,则重复尝试打开相同的路径将失败这有时可能会导致数据损坏。
    • @supercat 可以。但它没有。
    • 什么可以?我不知道有什么特定的实现可以做到这一点,但是如果一个实现的客户需要使用为操作系统编写的代码,其语义与新目标所需的语义不同,那么它可能很可能包含模拟逻辑来实现必要的语义。
    • @supercat 你的意思是什么?您是否建议更改答案?如果是这样,它是什么?如果不是,你为什么评论?不是侮辱,只是对您发表评论的目的感到困惑。
    • 在有意义的情况下,实现将 stdio 函数相当直接地映射到操作系统函数是很常见的,但通常需要查看实现的文档,而不仅仅是操作系统的文档才能知道什么期望在没有明确“最佳”行为的棘手情况下。
    【解决方案4】:

    C 实现存在于许多不同的平台上,它们的底层文件系统可能会以不同的方式处理这些极端情况。如果标准强制要求任何特定的极端情况行为,那么该语言只能在文件系统以这种方式运行的平台上实用。相反,该标准将此类问题视为超出其管辖范围(即使用其自己的术语“未定义行为”)。这并不意味着目标操作系统提供有用保证的实现不应该在实际情况下对程序做出此类保证,但实现设计者被认为比委员会更了解如何最好地为他们的客户服务。

    另一方面,实现暴露底层操作系统行为有时可能会有所帮助。例如,在没有明显“追加”模式的操作系统上,但需要“打开以追加”的代码可以执行“打开现有文件以进行写入”,然后是“寻找文件结尾”,尝试打开两个流以追加到同一个文件可能会导致数据损坏,当一个流写入文件的一部分,而另一个流随后重写该相同部分时。对于检测到该条件的实现来注入自己的逻辑以确保数据的平滑合并或阻止第二个打开请求,这可能会有所帮助。取决于应用程序的目的,任何一种行动方案都可能更好,但是 - 如上所述 - 选择不在标准的管辖范围内。

    【讨论】:

    • "使用它自己的术语,"未定义的行为"" 这不是真的。 UB在标准的管辖范围内;这是一个明确的声明,表明某事没有可靠的行为。相比之下,文件系统是否可以打开同一个文件更多的是文件系统的问题,而不是标准的问题。不是UB;它是“你必须检查它是否打开”。
    • 根据该标准的作者,“未定义的行为......还确定了可能的符合语言扩展的区域:实现者可以通过提供官方未定义行为的定义来扩充语言。”如果标准的作者不打算在他们的客户觉得有用的情况下这样做,为什么他们会提出这样的建议? C99 使用什么术语来表示在 C89 下的含义已经定义的操作,在通用平台的所有实现上都是相同的,但其 C89 规范可能不适用于奇怪的操作?
    • 文字墙。你的结论是什么?你想说啥?你在说什么?你的答案是什么?
    • @LightnessRacesBY-SA3.0:该标准将允许实施者以最能为实施者的客户服务的任何方式行事(或者,如果实施者不关心这一点,则无论实施者看到什么方式适合任何其他原因)。在任何给定情况下哪种行为最好取决于许多因素,客户和编译器编写者比委员会知道的要多得多。
    • @LightnessRacesBY-SA3.0:从标准的作者意图该术语的意义上说,它是“未定义的行为”,但不是某些编译器编写者倾向于解释它的方式。
    猜你喜欢
    • 1970-01-01
    • 2011-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-17
    • 1970-01-01
    • 2012-08-23
    • 1970-01-01
    相关资源
    最近更新 更多