Search code examples
cunixpipecygwinscanf

Unexpected behavior of pipes with scanf()


It's been a while since I last programmed in C, and I'm having trouble making pipes work. (For sake of clarity, I'm using Cygwin on Windows 7.) In particular, I need help understanding the behavior of the following example:

/* test.c */

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


int main() {

    char c;
    //scanf("%c", &c); // this is problematic

    int p[2];
    pipe(p);

    int out = dup(STDOUT_FILENO);

    // from now on, implicitly read from and write on pipe
    dup2(p[0], STDIN_FILENO);
    dup2(p[1], STDOUT_FILENO);

    printf("hello");
    fflush(stdout);

    // restore stdout
    dup2(out, STDOUT_FILENO);
    // should read from pipe and write on stdout
    putchar(getchar());
    putchar(getchar());
    putchar(getchar());
}

If I invoke:

echo abcde | ./test.exe

I get the following output:

hel

However, if I uncomment the scanf call, I get:

bcd

Which I can't explain. This is actually a very simplified version of a more complex program with a fork/exec structure that started behaving very bad. Despite not having cycles, it somehow began spawning infinite children in an endless loop. So, rules permitting, I'll probably need to extend the question with a more concrete case of use. Many thanks.


Solution

  • The stream I/O functions such as scanf generally perform buffering to improve performance. Thus, if you call scanf on the standard input then it will probably read more characters than needed to satisfy the request, and the extra will be waiting, buffered, for the next read.

    Swapping out the the underlying file descriptor does not affect previously buffered data. When you subsequently read the file again, you get data buffered the first time until those are exhausted, and only then do you get fresh data from the new underlying file.

    If you wish, you can turn off buffering of a stream via the setvbuf() function, before any I/O operations have been performed on it:

    int result = setvbuf(stdin, NULL, _IONBF, 0);
    if (result != 0) {
        // handle error ...
    }
    

    This is actually a very simplified version of a more complex program with a fork/exec structure that started behaving very bad. Despite not having cycles, it somehow began spawning infinite children in an endless loop.

    I don't see how that behavior would be related to what you've asked here.

    So, rules permitting, I'll probably need to extend the question with a more concrete case of use.

    That would be a separate question.