аномалия printf после “ветвления ()”

ОС: 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!

Почему это происходит? Это - корректное поведение, или действительно ли это - ошибка?

66
задан tom 29 August 2017 в 02:21
поделиться

3 ответа

Замечу, что - нестандартный заголовок; Я заменил его на , и код был скомпилирован чисто.

Когда вывод вашей программы направляется на терминал (экран), он буферизуется по строкам. Когда вывод вашей программы поступает в канал, он полностью буферизуется. Вы можете управлять режимом буферизации с помощью стандартной функции C setvbuf () и _IOFBF (полная буферизация), _IOLBF (буферизация строки) и _IONBF (без буферизации) режимы.

Вы можете продемонстрировать это в своей исправленной программе, направив вывод вашей программы, скажем, в cat . Даже с символами новой строки в конце строк printf () вы увидите двойную информацию. Если вы отправите его прямо в терминал, то вы увидите только один блок информации.

Мораль этой истории состоит в том, чтобы быть осторожным при вызове fflush (0); , чтобы очистить все буферы ввода-вывода перед разветвлением.


Построчный анализ в соответствии с запросом (фигурные скобки и т. Д. Удалены - и начальные пробелы удалены редактором разметки):

  1. printf ("Здравствуйте, мой pid -% d", getpid ());
  2. pid = fork ();
  3. if (pid == 0)
  4. printf ("\ nЯ был разветвлен!: D");
  5. sleep (3);
  6. else
  7. waitpid (pid, NULL, 0);
  8. printf («\ n% d был разветвлен!», Pid);

Анализ:

  1. Копирует «Здравствуйте, мой pid равен 1234» в буфер для стандартного вывода. Поскольку в конце нет новой строки, а вывод выполняется в режиме строчной буферизации (или режиме полной буферизации), на терминале ничего не отображается.
  2. Предоставляет нам два отдельных процесса с точно таким же материалом в буфере стандартного вывода.
  3. Дочерний элемент имеет pid == 0 и выполняет строки 4 и 5; родительский элемент имеет ненулевое значение для pid (одно из немногих различий между двумя процессами - возвращаемые значения из getpid () и getppid () равны еще два).
  4. Добавляет новую строку и «Меня разветвил!: D» в выходной буфер дочернего элемента. Первая строка вывода появляется на терминале; остальное хранится в буфере, поскольку вывод буферизируется по строке.
  5. Все останавливается на 3 секунды. После этого дочерний элемент обычно выходит через return в конце main. В этот момент остаточные данные в буфере стандартного вывода очищаются. Это оставляет позицию вывода в конце строки, так как новой строки нет.
  6. Родитель приходит сюда.
  7. Родитель ждет, когда ребенок умрет.
  8. Родитель добавляет новую строку и «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:

Обратите внимание, что когда вывод поступает на терминал , он буферизирован по строкам, поэтому строка «Hello» появляется перед fork () , и это была только одна копия. Когда вывод передается по конвейеру в cat , он полностью буферизуется, поэтому перед fork () ничего не появляется, и оба процесса имеют строку «Hello» в буфере, которая должна быть сброшена.

84
ответ дан 24 November 2019 в 15:02
поделиться

Причина в том, что без \ n в конце строки формата значение не сразу выводится на экран. Вместо этого он помещается в буфер внутри процесса. Это означает, что на самом деле он не печатается до тех пор, пока не будет выполнен вилка, поэтому вы напечатаете его дважды.

Добавление \ n заставляет буфер очищаться и выводиться на экран. Это происходит перед форком и, следовательно, печатается только один раз.

Вы можете заставить это произойти, используя метод fflush . Например,

printf( "Hello, my pid is %d", getpid() );
fflush(stdout);
25
ответ дан 24 November 2019 в 15:02
поделиться

fork () эффективно создает копию процесса. Если перед вызовом fork () у него были данные, которые были буферизованы, и родительский, и дочерний элемент будут иметь одинаковые буферизованные данные. В следующий раз, когда каждый из них сделает что-то для очистки своего буфера (например, напечатает новую строку в случае вывода терминала), вы увидите этот буферизованный вывод в дополнение к любому новому выводу, произведенному этим процессом. Так что, если вы собираетесь использовать stdio как в родительском, так и в дочернем, вам следует fflush перед форкованием, чтобы гарантировать отсутствие буферизованных данных.

Часто дочерний элемент используется только для вызова функции exec * . Поскольку это заменяет весь образ дочернего процесса (включая любые буферы), технически нет необходимости в fflush , если это действительно все, что вы собираетесь делать в дочернем процессе. Однако, если могут быть буферизованные данные, вы должны быть осторожны с обработкой сбоя exec. В частности, избегайте вывода ошибки на stdout или stderr с помощью любой функции stdio ( write в порядке), а затем вызовите _exit (или _Exit ) вместо вызова exit или просто возврат (что приведет к сбросу любого буферизованного вывода). Или вообще избежать проблемы, промыв перед разветвлением.

5
ответ дан 24 November 2019 в 15:02
поделиться
Другие вопросы по тегам:

Похожие вопросы: