ОС: Linux, Язык: чистый C
Я продвигаюсь в изучении C программирующий в целом и C, программирующий под UNIX в особом случае.
Я обнаружил странное (для меня) поведение printf()
функция после использования a 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!
Почему сделал второе, "Привет" представляют в виде строки, происходят в выводе ребенка?
Да, это точно, что распечатал родитель, когда это запустилось с родителем pid
.
Но! Если мы помещаем a \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!
Почему это происходит? Это - корректное поведение, или действительно ли это - ошибка?
Замечу, что
- нестандартный заголовок; Я заменил его на
, и код был скомпилирован чисто.
Когда вывод вашей программы направляется на терминал (экран), он буферизуется по строкам. Когда вывод вашей программы поступает в канал, он полностью буферизуется. Вы можете управлять режимом буферизации с помощью стандартной функции C setvbuf ()
и _IOFBF
(полная буферизация), _IOLBF
(буферизация строки) и _IONBF
(без буферизации) режимы.
Вы можете продемонстрировать это в своей исправленной программе, направив вывод вашей программы, скажем, в cat
. Даже с символами новой строки в конце строк printf ()
вы увидите двойную информацию. Если вы отправите его прямо в терминал, то вы увидите только один блок информации.
Мораль этой истории состоит в том, чтобы быть осторожным при вызове fflush (0);
, чтобы очистить все буферы ввода-вывода перед разветвлением.
Построчный анализ в соответствии с запросом (фигурные скобки и т. Д. Удалены - и начальные пробелы удалены редактором разметки):
printf ("Здравствуйте, мой pid -% d", getpid ());
pid = fork ();
if (pid == 0)
printf ("\ nЯ был разветвлен!: D");
sleep (3);
else
waitpid (pid, NULL, 0);
printf («\ n% d был разветвлен!», Pid);
Анализ:
pid == 0
и выполняет строки 4 и 5; родительский элемент имеет ненулевое значение для pid
(одно из немногих различий между двумя процессами - возвращаемые значения из getpid ()
и getppid ()
равны еще два). Теперь родительский элемент нормально завершает работу посредством возврата в конце 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:
Обратите внимание, что когда вывод поступает на терминал , он буферизирован по строкам, поэтому строка «Hello» появляется перед fork ()
, и это была только одна копия. Когда вывод передается по конвейеру в cat
, он полностью буферизуется, поэтому перед fork ()
ничего не появляется, и оба процесса имеют строку «Hello» в буфере, которая должна быть сброшена.
Причина в том, что без \ n
в конце строки формата значение не сразу выводится на экран. Вместо этого он помещается в буфер внутри процесса. Это означает, что на самом деле он не печатается до тех пор, пока не будет выполнен вилка, поэтому вы напечатаете его дважды.
Добавление \ n
заставляет буфер очищаться и выводиться на экран. Это происходит перед форком и, следовательно, печатается только один раз.
Вы можете заставить это произойти, используя метод fflush
. Например,
printf( "Hello, my pid is %d", getpid() );
fflush(stdout);
fork ()
эффективно создает копию процесса. Если перед вызовом fork ()
у него были данные, которые были буферизованы, и родительский, и дочерний элемент будут иметь одинаковые буферизованные данные. В следующий раз, когда каждый из них сделает что-то для очистки своего буфера (например, напечатает новую строку в случае вывода терминала), вы увидите этот буферизованный вывод в дополнение к любому новому выводу, произведенному этим процессом. Так что, если вы собираетесь использовать stdio как в родительском, так и в дочернем, вам следует fflush
перед форкованием, чтобы гарантировать отсутствие буферизованных данных.
Часто дочерний элемент используется только для вызова функции exec *
. Поскольку это заменяет весь образ дочернего процесса (включая любые буферы), технически нет необходимости в fflush
, если это действительно все, что вы собираетесь делать в дочернем процессе. Однако, если могут быть буферизованные данные, вы должны быть осторожны с обработкой сбоя exec. В частности, избегайте вывода ошибки на stdout или stderr с помощью любой функции stdio ( write
в порядке), а затем вызовите _exit
(или _Exit
) вместо вызова exit
или просто возврат (что приведет к сбросу любого буферизованного вывода). Или вообще избежать проблемы, промыв перед разветвлением.