LinuxForum.Fans
Тема

Загадка: 'Почтальон всегда стучит дважды' (printf и fork)

Автор: inneska 29 сообщений 7202 просмотров
#41692

Что мы увидим в результате работы программы?

text

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
        printf("tuk ");
        fork();
        printf("tuk ");
        exit(0);
}

И почему? ;)
Ответы можно в личку.

#41702

почему в личку? давайте здесь обсудим
кстати ответ можете посмотреть в man fork, и пример там есть:

text
#include <sys/wait.h>
       #include <assert.h>
       #include <stdio.h>
       #include <stdlib.h>
       #include <unistd.h>
       #include <string.h>

int main(int argc, char *argv[]) { int pipefd[2]; pid_t cpid; char buf;

assert(argc == 2);

if (pipe(pipefd) == -1) { perror("pipe"); exit(EXIT_FAILURE); }

cpid = fork(); if (cpid == -1) { perror("fork"); exit(EXIT_FAILURE); }

if (cpid == 0) { /* Child reads from pipe */ close(pipefd[1]); /* Close unused write end */

while (read(pipefd[0], &buf, 1) > 0) write(STDOUT_FILENO, &buf, 1); write(STDOUT_FILENO, &buf, 1);

write(STDOUT_FILENO, "\n", 1); close(pipefd[0]); _exit(EXIT_SUCCESS);

} else { /* Parent writes argv[1] to pipe */ close(pipefd[0]); /* Close unused read end */ write(pipefd[1], argv[1], strlen(argv[1])); close(pipefd[1]); /* Reader will see EOF */ wait(NULL); /* Wait for child */ exit(EXIT_SUCCESS); } }

кстати - как-то криво написано...
что-то вроде классики

text
int x = 7;
x = x++ + ++x;

чему будет равно x? и почему?

#41705
drBatty написал:
чему будет равно x? и почему?

Трудно предсказать, может дать разный результат в зависимости от опций компилятора.
Так лучше не писать :)

#41706

ЗЫЖ какое отношение имеет мой пример с кучей плюсов к fork'у? что удивительно - самое прямое: дело в том, что результат зависит от последовательности выполнения операций, и может выбираться компилятором произвольно. к примеру на любом процессоре начиная с iPentium мы можем выполнять 2 операции одновременно, чем компилятор и может воспользоватся. в данном случае ответ изменится. тут похожая ситуация - программа вызывает сама себя, причём ей абсолютно всё равно, дочка это или мамка. от этого-то и возникают непонятки. писать такой код - ИМХО моветон.

#41709
drBatty написал:
ситуация - программа вызывает сама себя, причём ей абсолютно всё равно, дочка это или мамка. от этого-то и возникают непонятки. писать такой код - ИМХО моветон.

Не совсем так.
Если это было бы так, то это была бы почти fork-бомба.
После fork, появляется новый процесс с тем же кодом, что и у родителя.
И конечно новый процесс начнет выполнять код не с самого начала, а со следующей инструкции после fork.
То есть функция printf будет вызвана ТРИ раза.
Один раз в отце до форка, второй раз в отце после форка и третий раз в сыне.

#41710

чему будет равно x? и почему?

не, ну по идее
x=7+9=16.
Не знаю насчёт компиляторов... а на каких будет по-другому?

По поводу той задачки - даже не знаю... ну наверное будет tuk tuk tuk? Вообще я с системными программками пока не возился, fork() не использовал - пока без надобности было.

inneska написал:
Один раз в отце до форка, второй раз в отце после форка и третий раз в сыне.

Ага, ну я так и подумал. Ответ правильный? :)

tuk tuk tuk tuk 3 75.00%

Эээ, те, кто проголосовали на 4 раза - можете объяснить, почему так? :)

#41730
Iced написал:
Эээ, те, кто проголосовали на 4 раза - можете объяснить, почему так?

Объясню позже, если никто другой не объяснит.
Могу намекнуть.
Вот эта программа будет работать по другому:

text

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
        write(1,"tuk ",4);
        fork();
        write(1,"tuk ",4);
        exit(0);
}
#41731
inneska написал:
[quote=drBatty]ситуация - программа вызывает сама себя, причём ей абсолютно всё равно, дочка это или мамка. от этого-то и возникают непонятки. писать такой код - ИМХО моветон.

Не совсем так.
Если это было бы так, то это была бы почти fork-бомба.
После fork, появляется новый процесс с тем же кодом, что и у родителя.
И конечно новый процесс начнет выполнять код не с самого начала, а со следующей инструкции после fork.
То есть функция printf будет вызвана ТРИ раза.
Один раз в отце до форка, второй раз в отце после форка и третий раз в сыне.[/quote]
Я тоже так думал, прежде чем проверять, однако у меня в Fedora 10,
gcc-4.3.2, tuk печатается 4 раза.

Пётр.

#41733

Ммм. Проверил. Скомпилировал. Тоже 4 раза печатает. Выходит, я ошибся. =) :rolleyes:

#41745
inneska написал:
То есть функция printf будет вызвана ТРИ раза.

а почему 4 tuk получилось?

Iced написал:
не, ну по идее
x=7+9=16.
Не знаю насчёт компиляторов... а на каких будет по-другому?

по стандарту тут может получится всё что угодно - дело в том, что последовательность действий выбирается произвольно, т.е. компилятор вовсе не обязан выполнять операции так, как вам этого хочется (как вам кажется), т.е. компилятор может выполнить оба инкремента как он захочет, а вовсе не так как написано. к примеру он имеет прао выполнить сначала второй инкремент, а потом первый. может и сразу выполнить оба инкремента. а получается да, 16. у мну - тоже.

#41746
drBatty написал:
по стандарту тут может получится всё что угодно - дело в том, что последовательность действий выбирается произвольно, т.е. компилятор вовсе не обязан выполнять операции так, как вам этого хочется (как вам кажется), т.е. компилятор может выполнить оба инкремента как он захочет, а вовсе не так как написано. к примеру он имеет прао выполнить сначала второй инкремент, а потом первый. может и сразу выполнить оба инкремента. а получается да, 16. у мну - тоже.

Я в книжке читал, по-моему, в Страуструпе, что ВСЕГДА получается 16. И что операции выполняются последовательно. Слева направо.
А зачем компиляторы так делают? Чтобы всех запутать?

#41749
drBatty написал:
а почему 4 tuk получилось?

Потом расскажу.
:rolleyes:
А во второй программе три раза. В чем разница?

#41751

inneska, разница в том, что в первой буфер сбрасывался только один раз, при выходе, а во второй данные записывались асинхронно?

#41759
Iced написал:
inneska, разница в том, что в первой буфер сбрасывался только один раз, при выходе, а во второй данные записывались асинхронно?

Да. только не "асинхронно", а как раз синхронно.

drBatty написал:
а получается да, 16. у мну - тоже.

у меня 17 получилось oO

#41782
Iced написал:
inneska, разница в том, что в первой буфер сбрасывался только один раз, при выходе, а во второй данные записывались асинхронно?

Да, у меня тоже возникло это предположение после чтения "man fork".
Если в варианте с printf, я, правда, всегда использую fprintf(stdout,...)
закончить строку формата символом '\n', то будут напечатаны три строки.

Пётр.

#41803
inneska написал:
А во второй программе три раза. В чем разница?

в кривом коде ИМХО. уже сам вижу в чём тут разница... только лично я такого никогда не напишу...

Peter написал:
Если в варианте с printf, я, правда, всегда использую fprintf(stdout,...)

какая разница между этими функциями? ЕМНИП во всех известных мне реализациях stdio эти функции генерируются в один и тот-же код. или нет?

inneska написал:
у меня 17 получилось blink

угу. и я про что.

#41821
drBatty написал:

[quote=Peter]Если в варианте с printf, я, правда, всегда использую fprintf(stdout,...)

какая разница между этими функциями? ЕМНИП во всех известных мне реализациях stdio эти функции генерируются в один и тот-же код. или нет?[/quote]
Разница, пожалуй, эстетическая, но я вообще избегаю использования
умолчаний, так надёжнее.
А в примере с printf я сначала попробовал вызывать в потомке
tcflush(1,TCOFLUSH), но это не помогло.

Пётр.

#41823
drBatty написал:
в кривом коде ИМХО. уже сам вижу в чём тут разница... только лично я такого никогда не напишу...

Что по твоему кривого в коде? Скажи где конкретно кривизна. :cool:
В том, что нет ветвления после форка?
А если мне надо что бы мама и дочка после форка выполняли одинаковый код? Зачем тогда ветвление?
Вот так:

text

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
        printf("tuk ");
        if (! fork()){
                printf("tuk ");
                exit(0);
        }
        else{
                printf("tuk ");
                wait(NULL);
                exit(0);
        }
}

программа выведет тоже 4 tuk

Peter написал:
Если в варианте с printf, я, правда, всегда использую fprintf(stdout,...)
закончить строку формата символом '\n', то будут напечатаны три строки.

Между printf и fprintf(stdout,...) конечно разницы нет.
А вот если написать fprintf(stderr,...) то, будет три раза tuk.
А вот символ '\n' - действительно помогает. Но тогда получается три строки, а не одна строка с тремя tuk.

Peter написал:
А в примере с printf я сначала попробовал вызывать в потомке
tcflush(1,TCOFLUSH), но это не помогло.

Ты очень близок к правильному ответу. fflush тоже может помочь.
Главное только понять где и почему надо принудительно сбросить буфер.

#41829

Оказалось, что помогает именно fflush, а tcflush - нет. Я использовал
tcflush по привычке работать с COM-портом и начал с вызова её перед
fork, а когда это не помогло, попробовал вызвать tcflush в потомке.
Вызов же fflush перед fork действительно сбрасывает буфер.

Точнее, я сначала вызвал tcflush перед fork, потом в потомке, потом
внимательнее посмотрел "man fork" и добавил '\n'.
Однако tcflush "discards data", а fflush сбрасывает буфер в указанный поток.

Пётр.

#41937

По мне тут все просто. Потомок наследует все окружение родителя в том числе буфера. Поскольку после первого вызова printf последовал вызов fork, первый тук остался в буфере и передался дочернему процессу. Дочерний процесс прицепил свой тук к уже имеющемуся и распечатал вместо одного тука два. Одно из решений - вставить строку fflush(stdout) перед вызовом форка. Кстати, весь процесс очень хорошо виден если все туки пронумеровать (tuk1, tuk2, tuk3)

Войдите или зарегистрируйтесь, чтобы ответить.