0

Does anyone know why the printf("Type q to quit") line prints twice in the terminal when I run this code:

#include <stdio.h>
#include <unistd.h>

int main (int argc, char *argv[])
{
    char run[2];
    run[0]='a';
    int pid=0;
    while (run[0]!= 'q')    
    {
        printf("Type q to quit \n");
        fgets (run, 2, stdin);
  
        pid=fork();
        //wait();
        if(pid==0) { break;}
    }
}

I would like the child to break from the loop and the parent to continue looping (to create new children). If I call wait() the execution ends after the first iteration regardless of whether I enter 'q' or not. Otherwise it works as expected but prints the "Type q to quit" line twice every time. Why does this happen?

Vishnu CS
  • 748
  • 1
  • 10
  • 24
Mike
  • 825
  • 3
  • 12
  • 30

3 Answers3

2

You have three bugs. First, the carriage return that the user types, after whatever letter, counts as input. Assuming the user types only one character per line, your fgets calls will alternate between returning the character you care about, and returning '\n'. You need to read and discard characters until you reach the end of each line of input, on each iteration. This is what is causing the double printouts.

Second, you need to test for 'q' in between reading from stdin and calling fork; right now, you fork once more after reading the 'q'. Right now this is invisible, but once the child process starts doing something useful it won't be.

Third, this program will go into an infinite loop if the user types ^D at it, because you're not checking for EOF.

Putting that together, corrected code looks like this. I've also fixed some style nits, and arranged so that the parent process exits immediately rather than dropping out of the for loop; this means that when control reaches the point marked with a comment, you know you're in a child process, rather than in the parent on its way out.

#include <stdio.h>
#include <unistd.h>

int
main(void)
{
    pid_t pid;
    int c;
    for (;;)
    {
        puts("Type q to quit");
        c = getchar();

        if (c == 'q')
            return 0;
        if (c == '\n')
            continue;
        while (c != EOF && c != '\n')
            c = getchar();
        if (c == EOF)
            return 0;

        pid = fork();
        if (pid == 0)
            break;
        else if (pid == -1)
        {
            perror("fork");
            return 1;
        }
    }

    /* control reaches this point only in child processes */
    return 0;
}
zwol
  • 135,547
  • 38
  • 252
  • 361
  • Any particular reason that you declare `c` as an `int` instead of `char`? – Mike Oct 24 '13 at 15:51
  • Also, what is the purpose of that while loop? The program is suppose to halt and wait for user input any time that `c` isn't equal to `EOF` and `\n` ? – Mike Oct 24 '13 at 15:59
  • 1
    `c` must be declared as `int` because `EOF` is (deliberately) a value outside the range of `char`. And the purpose of the while loop is to *discard* excess user input after the initial `getchar`, up to and including the first `\n` or EOF. Try it: it will not cause the program to halt. – zwol Oct 24 '13 at 17:18
  • Just to be clear, `getchar()` only halts and waits for user input when the buffer is empty, otherwise it reads in one character at a time from the buffer on each call, right? – Mike Oct 24 '13 at 19:25
  • 1
    Right. And, without special preparation (see [`tcsetattr`](http://linux.die.net/man/3/tcsetattr)), when this program is run in a terminal, the `stdin` buffer will always contain at least one complete line of user input including a terminating `'\n'`, except if the user forces an EOF, which is also handled. So you can't get stuck in the while loop. – zwol Oct 24 '13 at 21:32
1

When you type x<enter> on your terminal, you'll get (assuming ordinary encodings all over) two bytes sent to your process: one for the x, one for the newline.

Your fgets call reads at most one byte (so, the x), forks of a child that dies "instantly", prints the messages and calls fgets. fgets picks up where it left: it reads the newline char without blocking, your code forks again for no reason, and loops back.
At that point there's nothing left in the input stream, so fgets waits for I/O.

See for example: I am not able to flush stdin for ways to "clear out" the input stream that you could use here.

Community
  • 1
  • 1
Mat
  • 202,337
  • 40
  • 393
  • 406
0

You need to wait for these forked process down the line:

   int main (int argc, char *argv[])
   {
    char run[2];
    run[0]='a';
    int pid=0;
    int pids[256]; // should be enough.
    int j,i = 0;

    while (run[0]!= 'q')    
    {
        printf("Type q to quit \n");
        fgets (run, 2, stdin);

        pid=fork();
        //wait();
        if(pid==0) { break;}
        else { pids[i] = pid; i++; }
    }
    for (j = 0 ; j < i && pid != 0 ; j++)
       wait(pids[j]);
      if(pid == 0){
         // do something
      }

   }

You could instead have the child call another function (such as a program wrapper or even itself in non-forkable version) instead of breaking. The waiting would remain the same i.e. wait only once all forks are, well, forked.

Sebastien
  • 1,439
  • 14
  • 27