1.前言(引出现象)
我们看一段代码,
我们运行这段代码,
再次运行,并将打印结果重定向到文件log.txt中, 结果除了系统调用write的输出,其余输出都多打印一次。这是为什么呢?我们先了解几个知识点,再来回答这个问题。
2. C语言标准I/O函数的原理
通常,使用标准I/O的第一步是调用fopen()打开文件。
- fopen()函数不仅会打开文件,还会创建一个缓冲区(在读写模式下会创建两个缓冲区)以及一个包含文件和缓冲区数据的结构。
- 另外,fopen()返回一个指向该结构的指针,以便其他函数知道如何找到该结构。
- 假设把该指针赋给一个指针变量fp,我们就说fopen()函数“打开一个流”。如果以文本模式打开该文件,就会获得一个文本流;如果以二进制模式打开文件就会获得一个二进制流。
这个结构通常包含一个指定流中当前位置的文件指示器、包含错误和文件结尾的指示器、一个指向缓冲区开始处的指针、一个文件标识符和一个计数(统计实际拷贝进缓冲区的字节数)。
一般调用这些函数,文件中的缓冲大小数据块就被拷贝到缓冲区中,缓冲区的大小引实现而异。在初始化结构和缓冲区后,输入函数按照要求从缓冲区中读取数据,当它读取数据时,文件位置指示器被设置为指向刚读取字符的下一个字符。由于stdio.h系列的所有输入函数共用一个缓冲区,所以调用任何一个函数都将从上一次函数停止调用的位置开始。
摘抄自:《C Primer Plus》
3.缓冲区
C语言中FILE是结构体,文件类型的指针 - ostartech - 博客园 (cnblogs.com)
【Linux】分析缓冲区,刷新机制,FILE_内核模块 刷新缓冲-CSDN博客
深究标准IO的缓存 - orlion - 博客园 (cnblogs.com)
Linux 系统调用 —— fork 内核源码剖析 - 陈心朔 - 博客园 (cnblogs.com)
解释现象:
第一次程序运行,我们并没有重定向,输出的目标文件都是显示器,而显示器一般采用行缓冲策略,所以当输入换行符到缓冲区时,库函数才会调用系统接口,将用户层的缓冲区数据拷贝到内核层缓冲区。
第二次的程序运行,我们将输出的内容重定向到普通文件,普通文件的缓冲区一般采用全缓冲策略,要将缓冲区填满才会刷新缓冲区,由于printf、fprintf和fwrite输出的数据较小,不足以填满缓冲区,所以此时缓冲区的刷新是通过调用return,return调用exit刷新的。而在调用人return前,我们创建了一个子进程,子进程会继承父进程的数据和代码,为刷新的缓冲区也被继承了。所以库函数输出的内容重复了两次。 系统调用write输出的内容只打印一次,内核的缓冲区刷新机制我们暂且不讨论。