【问题标题】:Expected behaviour of freopen() with regards to buffering (setvbuf())?freopen() 关于缓冲 (setvbuf()) 的预期行为?
【发布时间】:2020-05-23 13:45:14
【问题描述】:

在尝试implement freopen() 时,我在标准中提出了一条规范,据我所知,该规范实际上并未指定任何内容。

所以...freopen() 将关闭流(忽略错误),清除其错误和 EOF 标志,重置宽方向,然后以给定模式重新打开流。这很清楚;这基本上是一个 fclose() / fopen()。即使没有这样定义,也很明显这是预期的结果。

但是,关于 setvbuf() 可能对流所做的事情,我有两个问题 - 设置用户分配的缓冲区和/或更改缓冲区策略。


问题 1。

1) freopen() 是否期望将事物恢复到默认状态,就好像它实际上调用了 fopen()?或者,无论用户通过setvbuf() 在旧流上设置什么,它是否有望延续到新流?这指的是缓冲内存和缓冲策略,但这里的主要问题是缓冲内存。

fclose() 的规范指定用户通过setvbuf() 与流关联的任何缓冲区都被解除关联,即现在可以由用户free()'d。

freopen() 仅指定它关闭与流关联的文件,而不是fclose()es 它。

那么,freopen() 之后,用户关联的缓冲内存是否仍然与流关联?


问题 2。

freopen() 可以用于FILE 结构,该结构在调用时实际上与打开的文件无关(因为尝试关闭文件的错误将被忽略)。

该文件结构可能是以前打开的流,具有用户分配的缓冲内存和缓冲策略。 freopen() 是为了遵守这些设置,即将缓冲存储器/策略与“重新”打开的文件重新关联,还是将结构重新初始化为默认值,假设用户 free()d 在 @987654337 之后使用缓冲存储器之前 @ing 文件?


我的看法。

看看 Q2,我看不到标准库有办法可靠地确定具有用户分配的缓冲内存的当前未打开的 FILE 结构是否仍然“拥有”该缓冲内存,或者用户是否已经收回了那段记忆。 (可以想象,该内存可能是本地的,即不是malloc() / free() 处理的内存列表的一部分,即使我愿意去那里——这将是标准库函数所期望的非常反常的工作。)

缓冲区政策的类似考虑因素。

因此,据我所知,唯一可靠的处理方式是freopen() 将“与指定流关联的任何文件”作为“真实”fclose(),并将缓冲内存/策略重新设置为默认值。

我的理解是否正确,或者 Q1 / Q2 有其他答案吗?

【问题讨论】:

  • freopen 为新的流返回一个新的 FILE*。作为参数传递的 FILE* 已关闭。复制缓冲区设置是出乎意料的。
  • @stark:根据定义,如果出现错误,返回的FILE * 要么是NULL,要么是作为参数传递给函数的FILE *( “......流的价值”)。所以不幸的是,它并没有那么明确。
  • 重用存储是明显的优化,但是函数的描述是新的流。
  • @stark:我不同意“明显”和“新流”。 (标准的措辞支持我。)如果这是您的答案,请发布它,但作为评论,这没有帮助。 (评论应该是为了澄清问题,而不是试图回答它。)
  • 感谢您完成这项工作...我将尝试并做出贡献,并为同一目标投入了一些时间。你在FILE 结构中添加了一个filename 槽来实现freopen(NULL, mode, stream) 的有问题的规范:我怀疑这既没有必要也不够,而且你在实现中有一个愚蠢的错误:stream->filename = (char *)malloc( strlen( filename ) )。一定要使用strdup(),它终于进入了下一个 C 标准。

标签: c language-lawyer standards standard-library c-standard-library


【解决方案1】:

C 标准没有说明缓冲状态以任何方式被修改。

整个C11 freopen() specification是(包括footnote 272):

7.21.5.4 freopen 函数

概要

1

     #include <stdio.h>
     FILE *freopen(const char * restrict filename,
          const char * restrict mode,
          FILE * restrict stream);

说明

2freopen 函数打开名称为指向的字符串的文件 to by filename 并将流指向的流与它相关联。 mode 参数的使用与 fopen 函数中一样。272)

3 如果filename 是空指针,freopen 函数会尝试 将流的模式更改为mode 指定的模式,就好像 当前与流关联的文件的名称已被使用。 允许哪些模式更改由实现定义(如果 任何),以及在什么情况下。

4freopen 函数首先尝试关闭任何在 与指定的流相关联。关闭文件失败是 忽略。流的错误和文件结束指示符是 清除。

退货

5freopen函数如果打开操作返回一个空指针 失败。否则,freopen 返回stream 的值。


272)freopen函数的主要用途是更改文件 与标准文本流(stderrstdinstdout)相关联,如 这些标识符不需要是可修改的左值 fopen 函数返回的可能被赋值。

对我来说,关键短语是将流指向的流与它相关联。stream 指向的预先存在的流有一个新文件与之相关的 - 仅此而已。通过不指定对缓冲的任何更改,这对我来说意味着保留当前的缓冲区状态,因为 freopen() 只是 将新文件和模式与 preexisting 关联溪流。根据我的阅读,只应对标准中明确指出的FILE * 流进行更改。

还要注意第 4 段:freopen 函数首先尝试关闭与指定流相关联的任何文件。同样,标准引用 指定流.

对我来说,结论似乎不可避免:freopen() 不会创建新流。它只是将预先存在的流指向一个新文件 - 仅此而已。

当前的实现支持此读数 - 当前流的缓冲区状态未修改。它们不会修改预先存在的流的缓冲状态。

GLIBC freopen() implementationthe OpenSolaris/Illumos implementation(很可能是当前的 Solaris 实现)似乎都不会修改原始缓冲的状态,而不是在关闭文件之前刷新任何缓冲区。

freopen() 函数似乎没有明确指定。 POSIX has this to say:

应用用途

freopen() 函数通常用于将与stdinstdoutstderr 关联的预先打开的流附加到其他文件。

由于当pathname 参数为NULL 时,实现不需要支持任何流模式更改,因此可移植应用程序不能依赖freopen() 来更改流模式,因此不鼓励使用此功能。该功能最初被添加到 ISO C 标准中,以便于将 stdinstdout 更改为二进制模式。由于模式中的 'b' 字符对 POSIX 系统没有影响,因此在 POSIX 应用程序中不需要使用该功能。但是,即使 'b' 被忽略,对 freopen (NULL, "wb", stdout) 的成功调用也会产生影响。特别是,对于常规文件,它会截断文件并将流的文件位置指示符设置为文件的开头。这些副作用可能是 ISO/IEC 9899:1999 标准中指定该功能的方式的意外结果,但除非或直到 ISO C 标准发生更改,成功调用 freopen (NULL, "wb", stdout) 的应用程序将在在以下情况下符合系统的意外方式:

{ appl file1; appl file2; } > file3

这将导致 file3 仅包含第二次调用 appl 的输出。

【讨论】:

  • 当作为参数传递的FILE * 不是当前fopen()ed 流时,这会让事情变得“有趣”。它可以指向一个 former 流,在这种情况下它会被正确初始化(包括缓冲区元信息,即是库控制的还是用户控制的)...但是由于流是 关闭的 此时,用户控制的缓冲内存可能可能已经是free()d。或者结构根本没有初始化......这是我最担心的一点。我(作为freopen() 实现者)期望在这里做什么?我看不到任何验证元数据的方法...
  • @DevSolar 全部正确。我唯一能想到的就是做一些测试,看看当前的实现如何处理这种情况。我怀疑如果您使用 GLIBC,关闭了一个流,释放了为该流分配的缓冲区,然后在该流上使用 freopen(),但它失败了,对提交的任何错误报告的响应将是“不要释放缓冲区对于预先存在的流,然后期望该流稍后工作。”
  • 在 Raspian 和 Mint 上进行测试时,我发现 glibc 在调用 freopen() 时保持缓冲策略缓冲。流显然已完全重新初始化。
猜你喜欢
  • 2017-10-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多