Thread: trouble with scanf and validating input

  1. #1
    Registered User
    Join Date
    May 2013
    Posts
    29

    trouble with scanf and validating input

    I need to use scanf to input an 8 digit number with no extra "junk" characters after the number (e.g. 12345678k), just the number, or else a prompt to re-enter. i know scanf isn't recommended but this is the second week of class and it's the only function of its kind we've learned so far ( besides getchar() for flushing the input buffer).

    this is what i have so far:

    Code:
    /* 
       This program prompts the user to setup a pin
       It handles invalid input properly 
    */
    
    #include <stdio.h>
    
    /* Complete the code for this program below */
    
    int main()
    {
        int acctNum, pinNum, temp, numDigits = 0;
        char ch;
    
        printf("Please enter your 8 digit account number: ");
        scanf("%d%c", &acctNum, &ch);
        
        /* Calculates numbers of digits in number entered */
        temp = acctNum;
       while(temp)
        {
          temp = acctNum/10;
          numDigits++;
       }
    
        while(numDigits != 8 || ch != '\n')
        {
            /* Flushes input buffer */
            while(getchar() != '\n');
        
           printf("Invalid account number. Account number must be 8 digits.\n\n");
            printf("Please enter your 8 digit account number: ");
            scanf("%d%c", &acctNum, &ch);
            
            /* Calculates numbers of digits in number entered */
            while(temp)
           {
             temp = acctNum/10;
             numDigits++;
          }
        }
        
        return 0;
    }
    problem is, i think i'm having trouble with the \n and scanf. here's what happens:

    If i input valid number first time = program ends succesfully

    If i input an invalid number first try (e.g. 123456789), i must press <enter> twice and then it prompts me to re-eneter, but every input after that is returned as invalid even if it is valid ( i also must press <enter> twice for all these inputs).

    where am i going wrong?

    thanks

  2. #2
    Registered User
    Join Date
    Nov 2010
    Location
    Long Beach, CA
    Posts
    5,909
    The code you posted does not match the symptoms you describe. You have an infinite loop in lines 20-24 (line 22 is wrong). That was easily fixed, however it does signify that this code is not the actual code you were running, thus the following may not actually solve your problem.

    scanf is tricky to get right. It's generally not ideal for user input. Your use of the %c operator is problematic. I assume it's to "eat" the newline, but that causes problems with your later loop (line 29) to flush the input buffer. Also your flush doesn't handle EOF. Try using fgets to read a line of input into a char array. Strip the newline if it's there (fgets puts it in the buffer), and check the string for valid input. I would either use a simple loop with isdigit and a digit counter, or strlen + strtol. Something like:
    Code:
    do {
        print "Enter an 8-digit account number"
        fgets buf
        strip new line
    } while strlen(buf) != 8 || strtol(buf, ...) fails
    // another way
    do {
        print "Enter an 8-digit account number"
        fgets buf
        strip new line    valid = true
        count digits
        check for non-digits
        if numdigits != 8 || non-digits in string
            valid = false
    } while !valid
    We have a couple useful FAQ articles on input:
    FAQ > Get a line of text from the user/keyboard (C) - Cprogramming.com
    FAQ > Flush the input buffer - Cprogramming.com

  3. #3
    Registered User
    Join Date
    May 2012
    Posts
    1,066
    Quote Originally Posted by fazio93 View Post
    If i input an invalid number first try (e.g. 123456789), i must press <enter> twice and then it prompts me to re-eneter,
    You only need to flush the input buffer if ch != '\n'.

    Quote Originally Posted by fazio93 View Post
    but every input after that is returned as invalid even if it is valid ( i also must press <enter> twice for all these inputs).
    Because you never reset "numDigits" (and "temp" after you fixed your infinite loops).

    Bye, Andreas

  4. #4
    Registered User
    Join Date
    May 2013
    Posts
    29
    Quote Originally Posted by anduril462 View Post
    The code you posted does not match the symptoms you describe.
    I copied and pasted from where I was working on it. Could it be a compiling issue? I'm using MinGW.


    Quote Originally Posted by anduril462 View Post
    You have an infinite loop in lines 20-24 (line 22 is wrong).
    I thought that "temp" eventually truncates to 0, ending the loop?

    Quote Originally Posted by anduril462 View Post
    scanf is tricky to get right. It's generally not ideal for user input. Your use of the %c operator is problematic. I assume it's to "eat" the newline, but that causes problems with your later loop (line 29) to flush the input buffer. Also your flush doesn't handle EOF. Try using fgets to read a line of input into a char array. Strip the newline if it's there (fgets puts it in the buffer), and check the string for valid input. I would either use a simple loop with isdigit and a digit counter, or strlen + strtol.
    I know scanf isn't the best to use, but I'm not sure if i should start using other functions i haven't learned yet. I know the %c operator is a crude and iffy use in this manner but it's the only thing i could use to make sure no junk is entered after the number.


    Quote Originally Posted by AndiPersti View Post
    You only need to flush the input buffer if ch != '\n'.
    I'm not really understanding this fully. My instructor gave us this code to help with this part of the assingment:

    Code:
    /* This program prompts the user for an integer input. 
       If an invalid input is inputed the input buffer is flushed
    */
    
    
    #include <stdio.h>
    int main()
    {
        int number;
        int extra;
        char ch;
        printf("Please enter a number:\n");
        scanf("%d", &number);
        extra=0;
        while ((ch = getchar()) != '\n' && ch != EOF) /* flushing the input buffer */ 
        {
            extra++;
        }
        if(extra!=0)
        {
            printf("Invalid input\n");
        }
        else
        {
            printf("You have entered %d\n",number);
        }
    return 0;
    }
    Quote Originally Posted by AndiPersti View Post
    Because you never reset "numDigits" (and "temp" after you fixed your infinite loops).
    i totally overlooked that. thanks. i guess that's one problem solved so far.

  5. #5
    Registered User
    Join Date
    Nov 2010
    Location
    Long Beach, CA
    Posts
    5,909
    Quote Originally Posted by fazio93 View Post
    I copied and pasted from where I was working on it. Could it be a compiling issue? I'm using MinGW.
    More likely you made changes in the editor and didn't save them before compiling, so what you see in the editor and what the compiler sees in that file on disk are different.
    Quote Originally Posted by fazio93 View Post
    I thought that "temp" eventually truncates to 0, ending the loop?
    That's what it should do, but look carefully:
    Code:
    temp = acctNum/10;
    // should be
    temp = temp / 10;
    // or
    temp /= 10;
    Quote Originally Posted by fazio93 View Post
    I know scanf isn't the best to use, but I'm not sure if i should start using other functions i haven't learned yet. I know the %c operator is a crude and iffy use in this manner but it's the only thing i could use to make sure no junk is entered after the number.
    If you are supposed to use scanf, then use it. If you think you'll get in trouble for using different methods, then don't. But some professors encourage or reward learning and using extra stuff on your own. That's a call you have to make (or email the prof/TA to find out their opinion).
    Quote Originally Posted by fazio93 View Post
    I'm not really understanding this fully. My instructor gave us this code to help with this part of the assingment:
    What AndiPersti was hinting at is that the %c operator reads in any character (unlike most scanf operators, which don't read white space like \n, \t or space). So if you type 12345678<enter>, the %c in your scanf call reads the '\n' into ch. Thus, the newline has been consumed, and there is nothing in the input buffer to be flushed. Your flush loop waits, looking for the next newline, hence having to hit enter twice. Hope that clears it up. Also, your professor's example is fine. You could wrap that inside a do-while loop if you want to repeat it.

  6. #6
    Registered User migf1's Avatar
    Join Date
    May 2013
    Location
    Athens, Greece
    Posts
    385
    This whole thing with scanf(), getchar(), etc has to do with the line-buffered nature of the stdin stream.

    To put it briefly, whatever the user types in is not consumed directly, but it goes into a buffer instead, until either the buffer gets full (not very likely in interactive input) or the user hits the ENTER key (this is the norm in most cases), or input has been ended prematurely (for example when EOF has occurred.. this also depends on the terminal).

    Unfortunately the buffer does not get flushed automatically. Also, without special care, scanf() ignores trailing blanks (ENTER included) but it does not remove them from the buffer. Moreover, scanf() consumes leading blanks.

    So when using scanf(), any leftovers in the input buffer (like the ENTER) feed the next scanf()/getchar(), if any, of the program.

    Furthermore, attempting to manually flush the buffer via fflush(stdin) yields to undefined behavior (it works on Windows platforms, but not on Linux/Unix for example). Thus, we should manually flush the input buffer in some other way, but only if the last character read was NOT the ENTER ('\n') and until we find that ENTER and consume it . That's what the code of your instructor does, with that while-loop.

    In general, scanf() is one of the most fragile functions in the standard library, and frankly nobody uses it for interactive input in real code. Even the C-FAQ advices against it, since ages.

    A better alternative is to use fgets() for reading the whole input line in a custom buffer, and then tokenize it according to your needs, using sscanf() for example (or strtok() and the strtoXXX() family of functions, or a custom routine altogether).

    I really hope they will teach you alternative ways to get interactive input, so you can ditch the fragile scanf() for that purpose as soon as possible.

    In the meanwhile, you may find useful this nice article that explains things in more detail, and provides code for getting interactive input from the console via a custom function.
    Last edited by migf1; 05-21-2013 at 05:48 PM.

  7. #7
    Registered User
    Join Date
    Nov 2010
    Location
    Long Beach, CA
    Posts
    5,909
    Welcome to the forum migf1. Just a couple things I found confusing/misleading/wrong in your post.
    Quote Originally Posted by migf1 View Post
    ...or input has been ended prematurely (for example when EOF has occurred.. this also depends on the terminal).
    EOF doesn't necessarily imply input ending prematurely, it can happen "on time", for example if I'm using the contents of a file to feed input to a program, as in foo < bar.input where bar.input doesn't have a trailing \n on the last line.
    Quote Originally Posted by migf1 View Post
    Unfortunately the buffer does not get flushed automatically. Also, without special care, scanf() ignores trailing blanks (ENTER included) but it does not remove them from the buffer. Moreover, scanf() consumes leading blanks.

    So when using scanf(), any leftovers in the input buffer (like the ENTER) feed the next scanf()/getchar(), if any, of the program.
    scanf does not always ignore trailing blanks without removing them. It may remove trailing blanks (read up on what a space char in the format string does). Neither does it always consume leading blanks. Rather, it stops parsing input when it encounters a character that doesn't fit the pattern of the format specifier it is looking for. That character is then left in the buffer, and scanf will attempt to match it to the next part of the format string, or leave it for the next input function to consume. The full details of what it reads in, stops on, or ignores can be found in the manual page: scanf(3): input format conversion - Linux man page.


    Quote Originally Posted by migf1 View Post
    Furthermore, attempting to manually flush the buffer via fflush(stdin) yields to undefined behavior (it works on Windows platforms, but not on Linux/Unix for example). Thus, we should manually flush the input buffer in some other way, but only if the last character read was NOT the ENTER ('\n'). That's what the code of your instructor does, with that while-loop.
    This is not so much about the OS as it is about the implementation of the standard library. It's the implementation that allows/disallows it. Many of the standard C libraries for Windows support fflush(stdin), but not all. Theoretically one could exist for *NIX that supports fflush(stdin). As you note, it's best to avoid this in any case, since the standard says it results in undefined behavior.

    The rest was quite nicely written.

  8. #8
    Registered User migf1's Avatar
    Join Date
    May 2013
    Location
    Athens, Greece
    Posts
    385
    Quote Originally Posted by anduril462 View Post
    Welcome to the forum migf1.
    Thanks!

    Just a couple things I found confusing/misleading/wrong in your post.

    EOF doesn't necessarily imply input ending prematurely, it can happen "on time", for example if I'm using the contents of a file to feed input to a program, as in foo < bar.input where bar.input doesn't have a trailing \n on the last line.
    That's true! However I wouldn't consider redirection to be interactive input (although your mileage may vary). I was thinking more about hitting say Ctr+D on a *nix terminal or Ctrl+Z on a Windows command prompt. TBH I don't remember if all implementations react uniformly when EOF is given on interactive input (I think *nix console signals an immediate stop, but I'm not sure).

    scanf does not always ignore trailing blanks without removing them. It may remove trailing blanks (read up on what a space char in the format string does). Neither does it always consume leading blanks. Rather, it stops parsing input when it encounters a character that doesn't fit the pattern of the format specifier it is looking for. That character is then left in the buffer, and scanf will attempt to match it to the next part of the format string, or leave it for the next input function to consume. The full details of what it reads in, stops on, or ignores can be found in the manual page: scanf(3): input format conversion - Linux man page.
    That's also true, and that's exactly the reason I have already written "when used without special care" in my original post.

    This is not so much about the OS as it is about the implementation of the standard library. It's the implementation that allows/disallows it. Many of the standard C libraries for Windows support fflush(stdin), but not all. Theoretically one could exist for *NIX that supports fflush(stdin). As you note, it's best to avoid this in any case, since the standard says it results in undefined behavior.
    Yeap, I wasn't implying that it depends on the OS. It just happens that all implementations I've tried on Windows do support fflush(stdin) (namely: visual studio/mingw32, and lcc/pelles c), while gcc which is the norm on *nix does not. I wasn't intending to confuse/mislead (sorry if I did).
    Last edited by migf1; 05-21-2013 at 07:23 PM.

  9. #9
    Registered User
    Join Date
    May 2013
    Posts
    29
    Quote Originally Posted by anduril462 View Post
    More likely you made changes in the editor and didn't save them before compiling, so what you see in the editor and what the compiler sees in that file on disk are different.
    *DOUBLE FACEPALM*

    I use jGRASP with a GCC compiler and have been hitting "compile" instead of "compile & link" the whole time", hence using the older code to test. i'm an idiot.

    the code now works fine except the only problem is still the the double <enter> in certain instances.

    here is the updated code:

    Code:
    /* 
        This program prompts the user to setup a pin
       It handles invalid input properly 
    */
    
    #include <stdio.h>
    
    /* Complete the code for this program below */
    
    int main()
    {
        int acctNum, pinNum, temp, numDigits = 0;
        char ch;
    
        printf("Please enter your 8 digit account number: ");
        scanf("%d%c", &acctNum, &ch);
        
        /* Calculates numbers of digits in number entered */
        temp = acctNum;
       while(temp)
        {
          temp = temp/10;
          numDigits++;
       }
    
        while(numDigits != 8 || ch != '\n')
        {
            while (getchar() != '\n' && getchar() != EOF);
            
           printf("Invalid account number. Account number must be 8 digits.\n\n");
            printf("Please enter your 8 digit account number: ");
            scanf("%d%c", &acctNum, &ch);
            
            numDigits = 0;
            temp = acctNum;
            
            /* Calculates numbers of digits in number entered */
            while(temp)
           {
             temp = temp/10;
             numDigits++;
          }
        }
    from your reply, i gather the objective is to always keep a "\n" character in the buffer so the getchar() doesn't wait for input? what exactly do i have to fix for this?


    thanks

  10. #10
    Registered User
    Join Date
    May 2012
    Posts
    1,066
    Quote Originally Posted by fazio93 View Post
    from your reply, i gather the objective is to always keep a "\n" character in the buffer so the getchar() doesn't wait for input?
    No, you just have to check whether you have already read the '\n' before you use the while-loop for flushing the buffer.

    Hint: In my first post (#3) I gave you already the code line you have to insert, just not in a legal syntax.

    Another problem is that you are calling getchar() twice:
    Code:
    while (getchar() != '\n' && getchar() != EOF);
    This means that if you enter an even number of non-numerical characters after your number (e.g. 123ab) the program waits for an additional input because the first getchar() will read the last invalid character and the second call will read the terminating newline character. But since the second call checks for EOF the loop will continue.
    So your teacher did use a variable for storing the result on purpose.

    Bye, Andreas
    Last edited by AndiPersti; 05-22-2013 at 09:57 AM.

  11. #11
    Registered User
    Join Date
    May 2013
    Posts
    29
    Quote Originally Posted by AndiPersti View Post
    No, you just have to check whether you have already read the '\n' before you use the while-loop for flushing the buffer.

    Hint: In my first post (#3) I gave you already the code line you have to insert, just not in a legal syntax.

    Another problem is that you are calling getchar() twice:
    Code:
    while (getchar() != '\n' && getchar() != EOF);
    This means that if you enter an even number of non-numerical characters after your number (e.g. 123ab) the program waits for an additional input because the first getchar() will read the last invalid character and the second call will read the terminating newline character. But since the second call checks for EOF the loop will continue.
    So your teacher did use a variable for storing the result on purpose.

    Bye, Andreas
    so this?

    Code:
    if(ch != '\n')
            {
                while(getchar() != '\n');
            }
    it's working from all the test combinations i've tried so far.

    so the while(getchar() != '\n'); was checking the buffer, but while checking it, it still accessed the '\n' character causing the problem with the 'waiting for input'? on the other hand, the if(ch != '\n') can check the buffer without "touching" the character? (sorry, i just like to make sure i understand everything completely)

  12. #12
    Registered User
    Join Date
    May 2012
    Posts
    1,066
    Quote Originally Posted by fazio93 View Post
    so the while(getchar() != '\n'); was checking the buffer, but while checking it, it still accessed the '\n' character causing the problem with the 'waiting for input'? on the other hand, the if(ch != '\n') can check the buffer without "touching" the character? (sorry, i just like to make sure i understand everything completely)
    Here's an example. Suppose the user enters "123456<Enter>". Now the input buffer looks like
    Code:
    1 2 3 4 5 6 \n
    Your first input function you use in your program--scanf()--will read all the digits, converts them to an integer and stores it in "acctNum" and then reads the next character following the digits and stores it in "ch". In the example that's the '\n'. Since the number has only 6 digits, the program enters the while loop which should flush the input buffer. But the input buffer is already empty and that's why getchar() waits for another input.

    On the other hand, if the user enters for example "123abc<Enter>" the input buffer will look like
    Code:
    1 2 3 a b c \n
    Again, all the numbers are consumed and "ch" will store the first non-numerical character ('a'). Now if you enter the while loop, getchar() will read the next character ('b'), discards it, reads the 'c', discards it and finally reads the '\n' which terminates the loop and the input buffer is empty.

    getchar() will always read a new character from the input buffer. If the buffer is empty it will wait until you enter one.

    Bye, Andreas

  13. #13
    Registered User
    Join Date
    May 2013
    Posts
    29
    Quote Originally Posted by AndiPersti View Post
    Here's an example. Suppose the user enters "123456<Enter>". Now the input buffer looks like
    Code:
    1 2 3 4 5 6 \n
    Your first input function you use in your program--scanf()--will read all the digits, converts them to an integer and stores it in "acctNum" and then reads the next character following the digits and stores it in "ch". In the example that's the '\n'. Since the number has only 6 digits, the program enters the while loop which should flush the input buffer. But the input buffer is already empty and that's why getchar() waits for another input.

    On the other hand, if the user enters for example "123abc<Enter>" the input buffer will look like
    Code:
    1 2 3 a b c \n
    Again, all the numbers are consumed and "ch" will store the first non-numerical character ('a'). Now if you enter the while loop, getchar() will read the next character ('b'), discards it, reads the 'c', discards it and finally reads the '\n' which terminates the loop and the input buffer is empty.

    getchar() will always read a new character from the input buffer. If the buffer is empty it will wait until you enter one.

    Bye, Andreas
    thanks, i understand it a bit better now. i guess my question is how exactly the added "if" statement fixes the program. if the while statement was by itself (like it was) wouldn't the whole while statement be skipped anyway because 'ch' did in fact equal '\n'? i would think the added "if" statement is just repetitive, but apparently it doesn't work like that. just having trouble making sense of it.

  14. #14
    Registered User
    Join Date
    Aug 2005
    Location
    Austria
    Posts
    1,990
    wouldn't the whole while statement be skipped anyway because 'ch' did in fact equal '\n'?
    No, the if(ch != '\n') statement checks the ch already read by the scanf() call.
    If that ch was not an '\n' then there were other characters following the integer and the buffer has to be flushed. If there was a '\n' read by the scanf() call then the buffer is already empty and it must not be flushed otherwise the while( getchar() != '\n'); would wait for another '\n'.
    Kurt

  15. #15
    Registered User
    Join Date
    May 2013
    Posts
    29
    Quote Originally Posted by ZuK View Post
    No, the if(ch != '\n') statement checks the ch already read by the scanf() call.
    If that ch was not an '\n' then there were other characters following the integer and the buffer has to be flushed. If there was a '\n' read by the scanf() call then the buffer is already empty and it must not be flushed otherwise the while( getchar() != '\n'); would wait for another '\n'.
    Kurt
    ok, i understand now. thank you everyone that helped.


Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Validating Input.
    By CrunchyMarvin in forum C Programming
    Replies: 7
    Last Post: 03-26-2013, 03:07 AM
  2. Validating unsigned integers with scanf()
    By Mr_Miguel in forum C Programming
    Replies: 7
    Last Post: 03-04-2008, 01:51 PM
  3. validating input using scanf
    By Axel in forum C Programming
    Replies: 6
    Last Post: 09-12-2005, 08:23 AM
  4. Validating an input
    By fkheng in forum C Programming
    Replies: 13
    Last Post: 06-22-2003, 11:25 PM
  5. Validating input
    By Barjor in forum C# Programming
    Replies: 7
    Last Post: 02-27-2003, 09:42 PM