操作系统: Linux,语言:纯C
我正在继续学习一般的C编程,在特殊情况下学习UNIX下的C编程。
在使用fork()
调用之后,我检测到printf()
函数有一个奇怪的行为(对我来说)。
代码
#include <stdio.h>
#include <system.h>
int main()
{
int pid;
printf( "Hello, my pid is %d", getpid() );
pid = fork();
if( pid == 0 )
{
printf( "\nI was forked! :D" );
sleep( 3 );
}
else
{
waitpid( pid, NULL, 0 );
printf( "\n%d was forked!", pid );
}
return 0;
}
输出
Hello, my pid is 1111
I was forked! :DHello, my pid is 1111
2222 was forked!
为什么第二个"Hello“字符串出现在孩子的输出中?
是的,这正是父进程启动时打印的内容,带有父进程的pid
。
但!如果我们在每个字符串的末尾放置一个\n
字符,我们将得到预期的输出:
#include <stdio.h>
#include <system.h>
int main()
{
int pid;
printf( "Hello, my pid is %d\n", getpid() ); // SIC!!
pid = fork();
if( pid == 0 )
{
printf( "I was forked! :D" ); // removed the '\n', no matter
sleep( 3 );
}
else
{
waitpid( pid, NULL, 0 );
printf( "\n%d was forked!", pid );
}
return 0;
}
输出
Hello, my pid is 1111
I was forked! :D
2222 was forked!
为什么会发生这种情况?这是正确的行为,还是错误?
发布于 2010-03-28 04:45:37
我注意到<system.h>
是一个非标准的头文件;我用<unistd.h>
替换了它,代码被干净地编译了。
当你的程序输出到终端(屏幕)时,它是行缓冲的。当您的程序输出到管道时,它是完全缓冲的。您可以通过标准C函数setvbuf()
和_IOFBF
(全缓冲)、_IOLBF
(行缓冲)和_IONBF
(无缓冲)模式来控制缓冲模式。
您可以在修改后的程序中演示这一点,方法是将程序的输出通过管道传输到cat
。即使在printf()
字符串的末尾有换行符,您也会看到双倍信息。如果您将其直接发送到终端,那么您将只看到一大堆信息。
这个故事的寓意是,在派生之前要小心调用fflush(0);
来清空所有的I/O缓冲区。
根据要求逐行分析(删除花括号等-并通过标记编辑器删除前导空格):
printf( "Hello, my pid is %d", getpid() );
pid = fork();
if( pid == 0 )
printf( "\nI was forked! :D" );
sleep( 3 );
else
waitpid( pid, NULL, 0 );
printf( "\n%d was forked!", pid );
分析:
pid == 0
并执行第4行和第5行;父进程的pid
值为非零值(这是两个进程之间的少数差异之一-来自getpid()
和getppid()
的返回值是另外两个)。父级现在通过main结尾处的返回正常退出,并刷新剩余数据;由于结尾处仍然没有换行符,因此光标位置在感叹号之后,shell提示出现在同一行上。
我看到的是:
Osiris-2 JL: ./xx
Hello, my pid is 37290
I was forked! :DHello, my pid is 37290
37291 was forked!Osiris-2 JL:
Osiris-2 JL:
PID数字是不同的-但整体外观是清晰的。在printf()
语句的末尾添加换行符(这很快就成为标准做法)会极大地改变输出:
#include <stdio.h>
#include <unistd.h>
int main()
{
int pid;
printf( "Hello, my pid is %d\n", getpid() );
pid = fork();
if( pid == 0 )
printf( "I was forked! :D %d\n", getpid() );
else
{
waitpid( pid, NULL, 0 );
printf( "%d was forked!\n", pid );
}
return 0;
}
我现在得到了:
Osiris-2 JL: ./xx
Hello, my pid is 37589
I was forked! :D 37590
37590 was forked!
Osiris-2 JL: ./xx | cat
Hello, my pid is 37594
I was forked! :D 37596
Hello, my pid is 37594
37596 was forked!
Osiris-2 JL:
请注意,当输出到达终端时,它是行缓冲的,因此“Hello”行出现在fork()
之前,并且只有一个副本。当输出通过管道传输到cat
时,它是完全缓冲的,因此在fork()
之前不会出现任何内容,并且两个进程都有要刷新的缓冲区中的“Hello”行。
发布于 2010-03-28 03:40:40
原因是在格式字符串的末尾没有\n
,值不会立即打印到屏幕上。相反,它是在进程中缓冲的。这意味着直到执行完fork操作之后,它才被实际打印出来,因此您会打印它两次。
但是,添加\n
会强制刷新缓冲区并将其输出到屏幕上。这发生在fork之前,因此只打印一次。
您可以使用fflush
方法强制执行此操作。例如
printf( "Hello, my pid is %d", getpid() );
fflush(stdout);
发布于 2010-03-28 19:59:24
fork()
有效地创建了该流程的副本。如果在调用fork()
之前,它具有缓冲的数据,则父对象和子对象都将具有相同的缓冲数据。下一次,当它们中的每一个刷新其缓冲区时(例如,在终端输出的情况下打印换行符),除了该进程产生的任何新输出之外,您还将看到缓冲的输出。因此,如果你打算在父进程和子进程中都使用stdio,那么你应该在forking之前使用fflush
,以确保没有缓冲的数据。
通常,子对象仅用于调用exec*
函数。因为它替换了完整的子进程映像(包括任何缓冲区),所以从技术上讲,如果这是您在子进程中要做的所有事情,那么就没有必要使用fflush
。但是,如果可能存在缓冲数据,则应小心处理exec故障。特别是,避免使用任何stdio函数(write
可以)将错误输出到stdout或stderr,然后调用_exit
(或_Exit
),而不是调用exit
或仅返回(这将刷新任何缓冲输出)。或者通过在分叉之前刷新来完全避免这个问题。
https://stackoverflow.com/questions/2530663
复制相似问题