freopen() 函数的预期行为与缓冲设置(setvbuf())有何关联?

7
为了实现freopen(), 我找到了标准中的一些规定,但是我并没有看到任何具体的规定。
所以... freopen()会关闭流(忽略错误),清除其错误和EOF标志,重置宽度方向,然后使用给定模式重新打开流。这很清楚;这基本上是一个fclose() / fopen()。即使它没有被定义为这样,很明显这就是意图。
但是,关于setvbuf()对流可能产生的影响,我有两个问题——设置用户分配的缓冲区和/或更改缓冲区策略。

问题1.

1)freopen()是否期望将事情恢复到默认状态,就好像它实际上调用了fopen()一样?还是期望将用户在旧流上通过setvbuf()设置的任何内容带到新流中?这涉及缓冲区内存和缓冲策略,但主要问题在于缓冲区内存。

fclose()的规范指定,用户通过setvbuf()将与流相关联的任何缓冲区都被取消关联,即现在可以由用户进行free()操作。

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

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


问题2.

freopen()可以在调用时使用未实际关联到打开文件的FILE结构(因为错误将被忽略而尝试关闭该文件)。

该文件结构可以是先前具有用户分配的缓冲区内存和缓冲区策略的已打开流。 freopen()是否要遵守这些设置,即重新关联缓冲区内存/策略与“重新”打开的文件,还是重新初始化结构以默认值,假设用户在之前的fclose()之后free()了缓冲区内存?


我的看法。

从Q2来看,我没有看到标准库可靠地确定一个当前未打开的FILE结构是否仍然“拥有”用户分配的缓冲区内存,或者用户是否已经回收了该内存。 (即使我愿意这样做,那些内存可能是本地的,即不是由malloc() / free()处理的内存列表的一部分,而且这将是标准库函数所期望的非常不同寻常的工作。)

对于缓冲区策略的考虑也类似。

因此,据我所见,处理事情的唯一可靠方法是,让freopen()将与指定流相关联的“任何文件”视为“真正的”fclose()并重新设置缓冲区内存/策略为默认值。

我的理解正确吗?还是有关于Q1 / Q2的其他答案?


1
freopen 为新流返回一个新的 FILE。作为参数传递的 FILE 将被关闭。复制缓冲区设置是不可预期的。 - stark
1
@stark:根据定义,如果出现错误,则返回的FILE *NULL,否则返回与传递给函数的相同的FILE *(“...流的值”)。所以很遗憾,情况并不是那么明确。 - DevSolar
重复使用存储是一种明显的优化,但该函数的描述是它是一个新流。 - stark
1
@stark:我不同意“显而易见”的观点,也不同意“新流派”的观点。(标准的措辞也支持我的观点。)如果这是你的答案,请发表评论,但作为评论,这并没有什么帮助。(评论应该用于澄清问题,而不是试图回答它。) - DevSolar
1
为您着手处理这项任务表示赞赏...我会尝试做出贡献,因为我已经为同样的目标投入了一些时间。您可以在FILE结构中添加一个filename插槽来实现freopen(NULL, mode, stream)的可疑规范:我怀疑这不是必要的也不足够,并且您在实现中有一个愚蠢的错误:stream->filename = (char *)malloc( strlen( filename ) )。一定要使用strdup(),它最终将进入下一个C标准。 - chqrlie
显示剩余3条评论
1个回答

2

C标准并没有规定缓冲区状态以任何方式被修改。

整个C11 freopen()规范(包括脚注 272)如下:

7.21.5.4 The freopen function

Synopsis

1

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

Description

2 The freopen function opens the file whose name is the string pointed to by filename and associates the stream pointed to by stream with it. The mode argument is used just as in the fopen function.272)

3 If filename is a null pointer, the freopen function attempts to change the mode of the stream to that specified by mode, as if the name of the file currently associated with the stream had been used. It is implementation-defined which changes of mode are permitted (if any), and under what circumstances.

4 The freopen function first attempts to close any file that is associated with the specified stream. Failure to close the file is ignored. The error and end-of-file indicators for the stream are cleared.

Returns

5 The freopen function returns a null pointer if the open operation fails. Otherwise, freopen returns the value of stream.


272) The primary use of the freopen function is to change the file associated with a standard text stream (stderr, stdin, or stdout), as those identifiers need not be modifiable lvalues to which the value returned by the fopen function may be assigned.

对我来说,关键短语是“将指向流的流与它相关联”。原先由 stream 指向的流与一个新文件相关联,就这样。由于未指定对缓冲区的任何更改,这意味着当前缓冲区状态被保留,因为 freopen() 只是将一个新文件和模式与 预先存在的 流相关联。根据我的理解,只应执行标准中明确记录的对 FILE * 流的更改。
另请注意第4段: freopen 函数首先尝试关闭与指定流相关联的任何文件。再次,标准涉及到 指定流
对我而言,结论似乎不可避免:freopen() 不会创建新流。它只是将预先存在的流指向一个新文件,仅此而已。
这种阅读方式——即当前流的缓冲状态不会被修改——得到了当前实现的支持。它们不会修改预先存在的流的缓冲状态。
无论是 GLIBC freopen() 实现 还是 OpenSolaris/Illumos 实现(很可能是当前 Solaris 实现)似乎都不会修改原始缓冲状态,除了在关闭文件之前刷新任何缓冲区。 freopen() 函数似乎规范不佳。 POSIX 这样说:

APPLICATION USAGE

The freopen() function is typically used to attach the pre-opened streams associated with stdin, stdout, and stderr to other files.

Since implementations are not required to support any stream mode changes when the pathname argument is NULL, portable applications cannot rely on the use of freopen() to change the stream mode, and use of this feature is discouraged. The feature was originally added to the ISO C standard in order to facilitate changing stdin and stdout to binary mode. Since a 'b' character in the mode has no effect on POSIX systems, this use of the feature is unnecessary in POSIX applications. However, even though the 'b' is ignored, a successful call to freopen (NULL, "wb", stdout) does have an effect. In particular, for regular files it truncates the file and sets the file-position indicator for the stream to the start of the file. It is possible that these side-effects are an unintended consequence of the way the feature is specified in the ISO/IEC 9899:1999 standard, but unless or until the ISO C standard is changed, applications which successfully call freopen (NULL, "wb", stdout) will behave in unexpected ways on conforming systems in situations such as:

{ appl file1; appl file2; } > file3

which will result in file3 containing only the output from the second invocation of appl.


1
当作为参数传递的FILE *不是当前已经打开的流时,情况就会变得“有趣”。它可能指向一个以前的流,在这种情况下,它将被正确初始化(包括缓冲区元信息,即库控制或用户控制)......但是在此时关闭了该流,用户控制的缓冲区内存可能已经被释放。或者结构根本没有初始化......这是我最担心的问题。作为freopen()实现者,我应该在这里做什么?我看不到任何验证元数据的方法... - DevSolar
@DevSolar 全都正确。我能想到的唯一一件事就是进行一些测试,看看当前的实现如何处理这种情况。我怀疑如果您使用GLIBC,关闭一个流,释放为该流分配的缓冲区,然后在该流上使用freopen(),并且它失败了,那么对于任何错误报告的响应都将是“不要释放预先存在的流的缓冲区,然后期望该流以后能够工作”。 - Andrew Henle
在Raspian和Mint上进行测试后,我发现glibc不会保留缓冲区策略或跨freopen()调用的缓冲区。流显然被完全重新初始化了。 - DevSolar

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