Search code examples
cscanfstdinrewind

How does scanf know if it should scan a new value?


I'm studying about how scanf works.

After scanned other type variable, char variable stores a white-space('\n') by getchar() or scanf("%c"). To prevent this, they should clear buffer. And I did it with rewind(stdin)

though stdin is rewinded previous input value is keeping in buffer. and I can do something with the previous value normally.(nothing runtime errors) but if I try scanf again, scanf will scan a new value even there is a normal value in buffer. how does scanf determine if it should scan a new value?

I found this mechanism with below code.

#include <stdio.h>
#define p stdin

int main() {
    int x;
    char ch;

    void* A, * B, * C, * D, * E;

    A = p->_Placeholder;
    printf("A : %p\n", A);//first time, it shows 0000
    scanf_s("%d", &x);

    B = p->_Placeholder;
    printf("B : %p\n", B);//after scanned something, I think it's begin point of buffer which is assigned for this process
    rewind(stdin);//rewind _Placeholder 

    C = p->_Placeholder;
    printf("C : %p\n", C);//it outputs the same value as B - length of x

    D = p->_Placeholder;
    printf("D : %c\n", ((char*)D)[0]);//the previous input value is printed successfully without runtime error. it means buffer is not be cleared by scanf
    scanf_s("%c", &ch, 1);//BUT scanf knows the _Placeholder is not pointing new input value, so it will scan a new value from console. How??

    E = p->_Placeholder;
    printf("E : %p\n", E);
    printf("ch : %c\n", ch);
}

Solution

  • There are some problems with you approach:

    • you use an undocumented, implementation specific member of the FILE object _Placeholder which may or may not be available on different platforms and whose contents are implementation specific anyway.
    • you use scanf_s(), which is a Microsoft specific so-called secure version of scanf(): this function is optional and may not be available on all platforms. Furthermore, Microsoft's implementation does not conform to the C Standard: for example the size argument passed after &ch is documented in VS with a type of UINT whereas the C Standard specifies it as a size_t, which on 64-bit versions of Windows has a different size.

    scanf() is quite tricky to use: even experienced C programmers get bitten by its many quirks and pitfalls. In your code, you test %d and %c, which behave very differently:

    • for %d, scanf() will first read and discard any white space characters, such as space, TAB and newlines, then read an optional sign + or -, it then expects to read at least one digit and stop when it gets a byte that is not a digit and leave this byte in the input stream, pushing it back with ungetc() or equivalent. If no digits can be read, the conversion fails and the first non digit character is left pending in the input stream, but the previous bytes are not necessarily pushed back.
    • processing %c is much simpler: a single byte is read and stored into the char object or the conversion fails if the stream is at end of file.

    Processing %c after %d is tricky if the input stream is bound to a terminal as the user will enter a newline after the number expected for %d and this newline will be read immediately for the %c. The program can ignore white space before the byte expected for %c by inserting a space before %c in the format string: res = scanf(" %c", &ch);

    To better understand the behavior of scanf(), you should output the return value of each call and the stream current position, obtained via ftell(). It is also more reliable to first set the stream to binary mode for the return value of ftell() to be exactly the number of bytes from the beginning of the file.

    Here is a modified version:

    #include <stdio.h>
    
    #ifdef _MSC_VER
    #include <fcntl.h>
    #include <io.h>
    #endif
    
    int main() {
        int x, res;
        char ch;
        long A, B, C, D;
    
    #ifdef _MSC_VER
        _setmode(_fileno(stdin), _O_BINARY);
    #endif
    
        A = ftell(stdin);
        printf("A : %ld\n", A);
    
        x = 0;
        res = scanf_s("%d", &x);
    
        B = ftell(stdin);
        printf("B : %ld, res=%d, x=%d\n", B, res, x);
    
        rewind(stdin);
        C = ftell(stdin);
        printf("C : %ld\n", C);
    
        ch = 0;
        res = scanf_s("%c", &ch, 1);
        D = ftell(stdin);
        printf("D : %ld, res=%d, ch=%d (%c)\n", D, res, ch, ch);
    
        return 0;
    }