操作系统: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!
为什么会这样?这是正确的行为,还是错误?
最佳答案
我注意到 <system.h>
是非标准标题;我将其替换为 <unistd.h>
并且代码编译干净。
当你的程序输出到终端(屏幕)时,它是行缓冲的。当程序的输出进入管道时,它是完全缓冲的。您可以通过标准 C 函数 setvbuf()
控制缓冲模式和 _IOFBF
(完全缓冲),_IOLBF
(行缓冲)和_IONBF
(无缓冲)模式。
您可以在修改后的程序中证明这一点,方法是将程序的输出通过管道传输到 cat
.即使 printf()
末尾有换行符字符串,你会看到双重信息。如果您直接将其发送到终端,那么您只会看到一大堆信息。
这个故事的寓意是要小心调用fflush(0);
在 fork 之前清空所有 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 是 1234”复制到标准输出缓冲区。因为末尾没有换行符,并且输出在行缓冲模式(或全缓冲模式)下运行,所以终端上不会出现任何内容。
- 为我们提供了两个独立的进程,在标准输出缓冲区中具有完全相同的 Material 。
- child 有
pid == 0
并执行第 4 行和第 5 行;父级的pid
具有非零值(这两个进程之间的少数区别之一 - 来自getpid()
和getppid()
的返回值是另外两个)。 - 在子级的输出缓冲区中添加一个换行符和“我被 fork 了!:D”。第一行输出出现在终端上;其余部分保存在缓冲区中,因为输出是行缓冲的。
- 一切停止 3 秒。在此之后, child 通过main结束时的return正常退出。此时,标准输出缓冲区中的剩余数据被刷新。由于没有换行符,这会将输出位置留在行尾。
- 家长来了。
- parent 等待 child 完成死亡。
- 父级添加了一个换行符,“1345 被 fork 了!”到输出缓冲区。在子级生成的不完整行之后,换行符将“Hello”消息刷新到输出。
parent现在通过main结束时的return正常退出,残留数据被flush;由于末尾仍然没有换行符,所以光标位置在感叹号之后,并且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”行。
关于c - "fork()"后printf异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2530663/