关于Linux一些IO的细节

写这篇博客的原因是在考试中遇到一个很 细 的题目。

题目

题目长这样

问最后的运行结果,当然有 fork 存在肯定是多种情况的,当时乍一看这一题,就很自信地写了

xbay ,xaby,xayb

结果却是一个大红叉子,我细数也就四次输出,为什么答案长度为 5??

我运行一遍之后,发现结果:

纳尼,真的是 5 个,哪来的五个呢。

细细品味一下,原来有个天坑。

分析

首先 Linux 的 C,它的缓冲区默认不初始化的,如果不初始化缓冲区,就会造成每隔一行才打印一次,或者是等到程序 exit 之后才打印。

如果在 Linux 下面有这样的语句

1
2
printf("input");
scanf("%s");

那么你大概率是看不到开头这个 input 的,只有程序结束了才能看到,原因就是调用的 IO 函数它会先把数据存到对应的 FILE 结构体上。

调试

我们启动 gdb 调试一下,断在第一个 putchar 下面,在进去之前我们可以看到,stdout 没有初始化。

然后 putchar 内部调用一个 __overflow 函数

最后进入到 _IO_file_overflow

在里面有一个分配缓冲区的动作,如果是程序自己设置的缓冲区,那么这个缓冲区会被分配到堆上面。

然后我们看看此时的 stdout 的结构

可以看到 _IO_write_ptr 在 _IO_write_base 的 +1 位置,那么这里面应该是存了一个 x,并且直到 putchar 调用结束,都不会输出出来。

也可以在缓冲区上看到这个字符。

这里选择追踪子进程,然后在 fork 结束之后看看结果。

程序在退出之后输出了 xay,很正常,因为仅仅跟踪父进程,确实就是输出这三个字符,但是此时我们可以打开 stdout 的结构体看看,发现缓冲区的数据也被复制了过来没有输出出来。

也就是说等会还会调用一个 putchar 再存到 缓冲区中,最后结束打印出两个字符就得到了正确答案。

可以看到缓冲区中有两个字符,然后我们进入 exit 看一看它会触发什么行为,可以发现 exit 当中,有一个函数为 _IO_cleanup,望文生义就是清理 IO 缓冲区用的嘛,主动调用 exit 或者是 main return 0 的时候都会触发。

最后当然就是调用 _IO_do_write 去打印缓冲区的内容。

因此这个题目其实只有两个情况,因为只有程序结束一瞬间才会打印,并且打印完,要么父进程先,要么子进程先。

这也就衍生出了一个问题,就是 Linux 的 c 程序默认不是实时打印,所以一般情况下我们需要初始化 stdout 的结构体。

1
setbuf(stdout,0);

初始化的 stdout 结构体会把缓冲区分配到 glibc 当中,就可以允许我们实时打印了,实时打印之后,答案就如我们所想的一样了。

或者每次输出之后,调用 fflush(stdout) 去刷新缓冲区达到实时打印的效果。

或者是使用 write 去打印,因为 write 函数直接走系统调用输出,不会经过 stdout 结构体,所以这是唯一一个不需要初始化缓冲区可以实时打印的函数。

总结

人有失手,马有失蹄,这个知识点其实以前知道的,但是万万没想到考试能出到这个知识点。

也算记忆更加深刻了吧,以后需要时刻注意。

文章目录
  1. 1. 题目
    1. 1.1. 分析
    2. 1.2. 调试
    3. 1.3. 总结
|