一个奇怪的程序

该程序生成的输出结果乍看颇令人费解

现象

1
2
3
4
5
6
7
8
9
#include<unistd.h>
#include<stdlib.h>
int main(int argc,char *argv[]){
printf("hello world\n");
write(STDOUT_FILENO,"Ciao\n",5);
if(fork() == -1)
exit(1);
exit(0);
}

当程序的标志输出定向到终端时,会看到预期效果:

当程序重定向标志输出到一个文件时,结果如下:

该输出有两件怪事,printf的输出行出现两次,且write的输出先于printf。

分析

  • 为什么printf的输出消息出现了两次?

    stdio缓冲区是在进程的用户空间中维护的,因此,通过fork创建子进程时会复制这些缓冲区。当标准输出定向到终端时,因为缺省为行缓冲,所以会立即显示函数printf输出的包含换行符的字符串。不过,当标准输出重定向到文件时,由于缺省为块缓冲,所以在该程序中,当调用fork时,prinf输出的字符串仍在父进程的stdio缓冲区中,并随子进程的创建而产生一份副本。父子进程调用exit时会刷新各自的缓冲区,从而导致重复的输出结果。write并未出现两次,这是因为write会将数据直接传给内核缓冲区,fork不会复制这一缓冲区。

  • write的输出先于printf

    因为write会将数据立即传给内核的高速缓存,而printf的输出则需要等到调用exit刷新stdio缓冲区时。

可以采用以下任一方法来避免重复的输出结果

  • 可以在调用fork之前使用函数fflush()来刷新stdio缓冲区。也可以使用setvbuf和setbuf来关闭stdio流的缓冲功能。
  • 子进程可以调用_exit()而非exit(),以便不再刷新缓冲区。这也验证了一个更为通用的原则:在创建子进程的应用中,典型情况仅有一个进程(一般为父进程)通过调用exit终止,而其他进程则调用_exit()终止,从而确保只有一个进程调用退出处理程序并刷新stdio缓冲区

在混合使用stdio函数和系统调用对同一文件进行IO处理时,需要特别谨慎!!!