2
votes

I wrote a simple program to test the scanf() function in C. It basically reads from the terminal, char by char, reprinting the return value and the char read; and to terminate if EOF/EOT is met or if a \n newline is read.

#include <stdio.h>
#include <stdbool.h>

int main(void) {
    char c; int ret;

    printf("Enter the chars to test: ");

    //LOOP (scan & print) only when return is not EOF and char is not newline
    while ( ((ret = scanf("%c", &c)) != EOF) && c!='\n' ) {
        printf("%i %c\n", ret, c);
    }
    return 0;
}

It terminates correctly, if newline (Enter) is pressed. But the it won't just terminate with a single Ctrl-D. A single Ctrl-D will flush the typed 'chars' and printing them. Then after that it will wait again for input, although an EOF has been sent with the Ctrl-D. If we press Ctrl-D again the 2nd time directly after the 1st (2x) or just Enter it will terminate. So you will need two consecutive Ctrl-D to terminate the program (or the loop in this case).

Example:

If you input 987 on the terminal, then press Enter; then 1 9, 1 8, 1 7 will be printed on newline each.

If you input 987 on the terminal, then press Ctrl-D; then 1 9 will be printed on the same line (because there is no Enter typed after inputing the 987 input), 1 8, 1 7 will be printed on newline. Then it will still wait for more inputs, unless it is terminated by directly entering a 2nd consecutive Ctrl-D or with a newline (Enter). So it (the program) will only stop (exit the loop) after a newline or 2nd consecutive Ctrl-D.

I am confused. Shouldn't a single Ctrl-D sent stop the loop here? What should I do to stop the program (scanf loop) after receiving just a single Ctrl-D?

I tested the code on Lubuntu 19.10 with gcc 9.2.1.

2
I think this is basically tied to the fact that stdin is typically endline-buffered, correct me if that's wrong.C. Dunn

2 Answers

2
votes

The issue is scanf() does not return EOF until there is no more input waiting and EOF is encountered. (your "%c" conversion specifier will otherwise accept any character, including the '\n' character, as valid and consider it a successful conversion)

When you type a line of characters, e.g. "abcdefg" and attempt to press Ctrl+d (indicating end-of-input), the input ("abcdefg") is processed and when 'g' is reached, then scanf() blocks waiting on your next input. (because a successful conversion took place and no matching or input failure occurred)

Once Ctrl+d is typed a 2nd time (indicating end-of-input), when there is no input to process, EOF is reached before the first successful conversion and an input-failure occurs,scanf() then returns EOF.

See: man 3 scanf -- RETURN VALUE section and C11 Standard - 7.21.6.2 The fscanf function(p16)

0
votes

Test the returned value for one, not EOF. When eof is reached first time, zero is returned. The next scanf after zero returns EOF.

while ( ((ret = scanf("%c", &c)) == 1) && c!='\n' ) {
    printf("%i %c\n", ret, c);
}

This behavior is specified for the standard C library.

On success, the function returns the number of items of the argument list successfully filled. This count can match the expected number of items or be less (even zero) due to a matching failure, a reading error, or the reach of the end-of-file.

If a reading error happens or the end-of-file is reached while reading, the proper indicator is set (feof or ferror).

You can add if (feof(stdin) != 0) break; into the loop body, if you use more complex input format.

If you read chars only, is it not better use fgetc?