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 60

3 ответа:

замечу, что <system.h> - это нестандартный заголовок, Я заменил его с <unistd.h> и код компилируется без ошибок.

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

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

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


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

  1. printf( "Hello, my pid is %d", getpid() );
  2. pid = fork();
  3. if( pid == 0 )
  4. printf( "\nI was forked! :D" );
  5. sleep( 3 );
  6. else
  7. waitpid( pid, NULL, 0 );
  8. printf( "\n%d was forked!", pid );

анализ:

  1. копирует "Hello, my pid is 1234" в буфер для стандартного вывода. Потому что в конце нет новой строки, а вывод выполняется в буферизованной строке режим (или режим полной буферизации), ничего не появляется на терминале.
  2. дает нам два отдельных процесса, с точно таким же материалом в буфере stdout.
  3. ребенок pid == 0 и выполняет строки 4 и 5; родитель имеет ненулевое значение для pid (одно из немногих различий между двумя процессами - возвращаемые значения getpid() и getppid() больше двух).
  4. добавляет новую строку и "я раздвоился! :D " к выходному буферу ребенка. Первый строка вывода появляется на терминале; остальное удерживается в буфере, так как выход буферизован линией.
  5. все останавливается на 3 секунды. После этого ребенок обычно выходит через возврат в конце 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: 

номера ПИД различны-но общее возникновение ясно. Добавление новых строк в конец 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 или просто возврат (который будет очищать любой буферизованный выход). Или вообще не топить до разветвления.