Printf аномалия после "fork()"
ОС: Linux, язык: чистый 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 %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!
Почему это происходит? Это правильное поведение, или это ошибка?
Ответы
Ответ 1
Я отмечаю, что <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 1234" в буфер для стандартного вывода. Поскольку в конце нет новой строки, а выход работает в режиме с буферизацией в линии (или режиме с полной буферизацией), на терминале ничего не появляется.
- Предоставляет нам два отдельных процесса с точно таким же материалом в буфере stdout.
- Ребенок имеет
pid == 0
и выполняет строки 4 и 5; у родителя есть ненулевое значение для pid
(одно из немногих различий между двумя процессами - возвращаемые значения из getpid()
и getppid()
- еще два).
- Добавляет новую строку и "Я был разветвлен!: D" в выходной буфер дочернего элемента. Первая строка вывода появляется на терминале; остальное удерживается в буфере, так как вывод буферизируется по строке.
- Все останавливается на 3 секунды. После этого ребенок обычно выезжает через возвращение в конце основного. В этот момент остаточные данные в буфере stdout очищаются. Это оставляет выходное положение в конце строки, поскольку нет новой строки.
- Здесь приходит родитель.
- Родитель ждет, пока ребенок закончит умирание.
- Родитель добавляет новую строку и "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:
Номера 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:
Обратите внимание, что когда вывод идет на терминал, он буферизируется по строке, поэтому перед "t225" появляется строка "Hello" , и была только одна копия. Когда вывод передан на канал cat
, он полностью буферизирован, поэтому перед fork()
ничего не появляется, и оба процесса имеют строку "Hello" в буфере, который должен быть сброшен.
Ответ 2
Причина в том, что без \n
в конце строки формата значение не будет немедленно распечатано на экране. Вместо этого он буферизуется в процессе. Это означает, что он фактически не печатается до тех пор, пока после операции вилки, поэтому вы дважды печатаете его.
Добавление \n
, хотя заставляет буфер очищаться и выводиться на экран. Это происходит перед вилкой и, следовательно, печатается только один раз.
Вы можете заставить это произойти с помощью метода fflush
. Например
printf( "Hello, my pid is %d", getpid() );
fflush(stdout);
Ответ 3
fork()
эффективно создает копию процесса. Если перед вызовом fork()
у него были данные, которые были буферизованы, как родительский, так и дочерний будут иметь те же буферизованные данные. В следующий раз, когда каждый из них сделает что-то, чтобы очистить свой буфер (например, печать новой строки в случае вывода терминала), вы увидите этот буферизованный вывод в дополнение к любому новому выпуску, создаваемому этим процессом. Поэтому, если вы собираетесь использовать stdio как в родительском, так и в дочернем, вы должны fflush
перед форсированием, чтобы убедиться, что нет буферизованных данных.
Часто дочерний элемент используется только для вызова функции exec*
. Поскольку это заменяет полный образ дочернего процесса (включая любые буферы), технически нет необходимости в fflush
, если это действительно все, что вы собираетесь делать в этом случае. Однако, если могут быть буферизованные данные, тогда вы должны быть осторожны в том, как обрабатывается сбой exec. В частности, избегайте печати ошибки в stdout или stderr с помощью любой функции stdio (write
в порядке), а затем вызывайте _exit
(или _exit
), а не вызываете exit
или просто возвращаете (который будет очищать любые буферные вывод). Или вообще избегайте проблемы путем промывки перед форкированием.
Ответ 4
У меня та же проблема: ![enter image description here]()
Это дает мне: ![enter image description here]()
По сути, я понял комментарии выше и очень помог мне понять, как работают вилки; И здесь происходит то, что у меня есть родительский pid, который умирает, когда основная функция умирает, и у меня также есть дочерний pid, который умирает первым и возвращается к родителю.
Когда форк запускается, у меня теперь есть 2 процесса, которые выполняются: 1- parent 2- child, он буферизует строку родителя, затем child, и затем child, умирает, возвращается к parent и только тогда показывает\п.
Мой ожидаемый результат будет состоять в том, чтобы фактически показать \n как часть строки... и иметь две отдельные строки со строкой
>string1 \n
>string2 \n
>
и я получаю это:
>string1 string2 \n
>
Я пытался с fflush, но тот же вывод, что и раньше...
![enter image description here]()
>"Show me the new line before the pid child dies Show me the new line before the pid child dies"
>
(как видно из предыдущих комментариев, fflush будет пытаться выдвинуть его, но \n будет напечатан только один раз, и я хочу, чтобы он печатался дважды, как часть строки, а не только один раз как конец моего " flush/vector с процессами и т.д. "- не знаю, как это назвать, просто строка, в которой есть \n, но здесь \n работает как часть работы с форком и будет последней вещью, которую он выполняет, к сожалению...)
Как мне сделать так, чтобы я получил следующий вывод:
>"Show me the new line before the pid child dies \n"
> Show me the new line before the pid child dies \n"
>