为什么当重定向到文件时,stdout 需要显式刷新?
- 2024-10-21 09:14:00
- admin 原创
- 111
问题描述:
的行为printf()
似乎取决于的位置stdout
。
如果
stdout
被发送到控制台,那么printf()
就会进行行缓冲,并且在打印换行符后刷新。如果
stdout
重定向到文件,则除非fflush()
调用,否则缓冲区不会被刷新。此外,如果在重定向到文件
printf()
之前使用stdout
,则后续写入(到文件)将进行行缓冲,并在换行符后刷新。
什么时候是stdout
行缓冲,什么时候fflush()
需要调用?
每个的最小示例:
void RedirectStdout2File(const char* log_path) {
int fd = open(log_path, O_RDWR|O_APPEND|O_CREAT,S_IRWXU|S_IRWXG|S_IRWXO);
dup2(fd,STDOUT_FILENO);
if (fd != STDOUT_FILENO) close(fd);
}
int main_1(int argc, char* argv[]) {
/* Case 1: stdout is line-buffered when run from console */
printf("No redirect; printed immediately
");
sleep(10);
}
int main_2a(int argc, char* argv[]) {
/* Case 2a: stdout is not line-buffered when redirected to file */
RedirectStdout2File(argv[0]);
printf("Will not go to file!
");
RedirectStdout2File("/dev/null");
}
int main_2b(int argc, char* argv[]) {
/* Case 2b: flushing stdout does send output to file */
RedirectStdout2File(argv[0]);
printf("Will go to file if flushed
");
fflush(stdout);
RedirectStdout2File("/dev/null");
}
int main_3(int argc, char* argv[]) {
/* Case 3: printf before redirect; printf is line-buffered after */
printf("Before redirect
");
RedirectStdout2File(argv[0]);
printf("Does go to file!
");
RedirectStdout2File("/dev/null");
}
解决方案 1:
刷新stdout
由其缓冲行为决定。缓冲可以设置为三种模式:(_IOFBF
完全缓冲:等待直到fflush()
可能为止)、_IOLBF
(行缓冲:换行触发自动刷新)和(始终使用直接写入)。“对这些特性的支持由实现定义,并且可能通过和函数_IONBF
受到影响。”[C99:7.19.3.3]setbuf()
`setvbuf()`
“在程序启动时,三个文本流是预定义的,不需要明确打开 - 标准输入(用于读取常规输入)、标准输出(用于写入常规输出)和标准错误(用于写入诊断输出)。最初打开时,标准错误流未完全缓冲;当且仅当可以确定流不引用交互式设备时,标准输入和标准输出流才会完全缓冲。” [C99:7.19.3.7]
观察到的行为的解释
因此,实现会执行一些特定于平台的操作来决定是否stdout
要进行行缓冲。在大多数 libc 实现中,此测试是在首次使用流时进行的。
行为#1很容易解释:当流用于交互式设备时,它是行缓冲的,并且
printf()
会自动刷新。现在也预计会出现情况 #2:当我们重定向到一个文件时,流会被完全缓冲,并且不会被刷新
fflush()
,除非您向其中写入大量数据。最后,对于仅对底层 fd 执行一次检查的实现,我们也理解了案例 #3。由于我们强制在第一个 stdout 的缓冲区进行初始化
printf()
,因此 stdout 获得了行缓冲模式。当我们将 fd 换出以转到文件时,它仍然是行缓冲的,因此数据会自动刷新。
一些实际的实现
每个 libc 在解释这些要求方面都有自己的自由,因为 C99 没有指定什么是“交互式设备”,POSIX 的 stdio 条目也没有对此进行扩展(除了要求 stderr 处于打开状态以供读取之外)。
Glibc。请参阅filedoalloc.c:L111。在这里我们用来
stat()
测试 fd 是否为 tty,并相应地设置缓冲模式。(这是从 fileops.c 调用的。)stdout
最初有一个空缓冲区,它是在第一次使用流时根据 fd 1 的特征分配的。BSD libc。非常相似,但代码更简洁!请参阅makebuf.c 中的这一行
解决方案 2:
您错误地组合了缓冲和非缓冲 IO 函数。这种组合必须非常小心,尤其是当代码必须是可移植的时。(编写不可移植的代码是不好的……)
最好避免在同一个文件描述符上组合缓冲和非缓冲 IO。
缓冲 I/O: fprintf()
, fopen()
, fclose()
, freopen()
...
无缓冲 IO : write()
,,,...open()
`close()`dup()
当您使用dup2()
重定向 stdout 时。该函数不知道由 填充的缓冲区fprintf()
。因此,当dup2()
关闭旧描述符 1 时,它不会刷新缓冲区,并且内容可能会刷新到不同的输出。在您的案例 2a 中,它被发送到/dev/null
。
解决方案
对于你的情况,最好使用freopen()
而不是dup2()
。这解决了你所有的问题:
它刷新原始流的缓冲区
FILE
。(情况 2a)它根据新打开的文件设置缓冲模式。(情况 3)
这是您的函数的正确实现:
void RedirectStdout2File(const char* log_path) {
if(freopen(log_path, "a+", stdout) == NULL) err(EXIT_FAILURE, NULL);
}
不幸的是,使用缓冲 IO 您无法直接设置新创建文件的权限。您必须使用其他调用来更改权限,或者可以使用不可移植的 glibc 扩展。请参阅fopen() man page
。
解决方案 3:
您不应该关闭文件描述符,因此如果您希望仅在文件中打印消息,请删除close(fd)
并关闭它。stdout_bak_fd
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件