Print аномалия после " вилки()"
ОС: Linux, язык: pure C
я продвигаюсь вперед в изучении программирования на C в целом и программирования на C под UNIX в частном случае.
я обнаружил странное (для меня) поведение printf()
функции после использования fork()
звонок.
код
#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 %dn", 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!
почему это происходит? Это правильное поведение, или это ошибка?
3 ответа:
замечу, что
<system.h>
- это нестандартный заголовок, Я заменил его с<unistd.h>
и код компилируется без ошибок.когда выход вашей программы идет к терминалу (экрану), он буферизован линией. Когда выходные данные вашей программы поступают в канал, они полностью буферизуются. Вы можете управлять режимом буферизации с помощью стандартной функции C
setvbuf()
и_IOFBF
(полная буферизация),_IOLBF
(буфферизацию) и_IONBF
(без буферизации) режимах.вы могли продемонстрируйте это в своей пересмотренной программе, передав выходные данные своей программы, скажем,
cat
. Даже с новыми строками в концеprintf()
строки, вы увидите двойную информацию. Если вы отправите его прямо на терминал, то вы увидите только один много информации.мораль этой истории состоит в том, чтобы быть осторожным, чтобы позвонить
fflush(0);
чтобы очистить все буферы ввода-вывода до разветвления.
построчный анализ, как и требовалось (фигурные скобки и т. д. удалены-и ведущие пробелы удалены редактором разметки):
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 );
анализ:
- копирует "Hello, my pid is 1234" в буфер для стандартного вывода. Потому что в конце нет новой строки, а вывод выполняется в буферизованной строке режим (или режим полной буферизации), ничего не появляется на терминале.
- дает нам два отдельных процесса, с точно таким же материалом в буфере stdout.
- ребенок
pid == 0
и выполняет строки 4 и 5; родитель имеет ненулевое значение дляpid
(одно из немногих различий между двумя процессами - возвращаемые значенияgetpid()
иgetppid()
больше двух).- добавляет новую строку и "я раздвоился! :D " к выходному буферу ребенка. Первый строка вывода появляется на терминале; остальное удерживается в буфере, так как выход буферизован линией.
- все останавливается на 3 секунды. После этого ребенок обычно выходит через возврат в конце main. В этот момент, остаточные данные в буфер вывода сбрасывается. Это оставляет выходную позицию в конце строки, так как нет новой строки.
- родитель приходит сюда.
- родитель ждет, пока ребенок закончит умирающий.
- родитель добавляет новую строку и "1345 был раздвоен!- в выходной буфер. Новая строка сбрасывает сообщение "Hello" на выход после неполной строки, сгенерированной дочерним элементом.
родитель теперь обычно выходит через возврат в конце main, и остаточные данные сбрасываются; поскольку в конце все еще нет новой строки, позиция курсора находится после восклицательного знака, и приглашение оболочки появляется на том же самом линия.
что я вижу:
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:
номера ПИД различны-но общее возникновение ясно. Добавление новых строк в конец
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" в буфере, который нужно сбросить.
причина, почему это без
\n
в конце строки форматирования значение не сразу выводится на экран. Вместо этого он буферизуется в процессе. Это означает, что он фактически не печатается до тех пор, пока после операции вилки, следовательно, вы получите его напечатанным дважды.добавлять
\n
хотя заставляет буфер быть сброшенным и выведенным на экран. Это происходит до вилки и, следовательно, печатается только один раз.вы можете заставить это произойти используя
fflush
метод. Напримерprintf( "Hello, my pid is %d", getpid() ); fflush(stdout);
fork()
эффективно создает копию процесса. Если перед вызовомfork()
, Он имел данные, которые были буферизованы, и родитель и ребенок будут иметь те же буферизованные данные. В следующий раз, когда каждый из них сделает что-то, чтобы очистить свой буфер (например, печать новой строки в случае вывода терминала), вы увидите, что буферизованный выход в дополнение к любому новому выходу, полученному этим процессом. Так что если вы собираетесь использовать stdio в обоих родителя и ребенка, то вы должныfflush
перед разветвление, чтобы убедиться, что нет буферизованных данных.часто ребенок используется только для вызова . Поскольку это заменяет полный образ дочернего процесса (включая любые буферы), технически нет необходимости
fflush
если это действительно все, что вы собираетесь делать в детстве. Однако, если могут быть буферизованные данные, то вы должны быть осторожны в том, как обрабатывается ошибка exec. В частности, избегайте печати ошибки в stdout или stderr с помощью любой функции stdio (write
в порядке), а затем позвоните_exit
(или_Exit
) вместо того, чтобы звонитьexit
или просто возврат (который будет очищать любой буферизованный выход). Или вообще не топить до разветвления.