Thread: Number user input

Hybrid View

Previous Post Previous Post   Next Post Next Post
  1. #1
    Registered User jimtuv's Avatar
    Join Date
    May 2010
    Location
    Sylvania, Ohio
    Posts
    94

    Number user input

    I was going over number input in the FAQ here. I am about half done reading it and wanted to try out some of the early examples both good and bad to understand what is exactly going on. Here is a function I made. I added a min and max to limit the size of the number.

    The call looks like this:
    Code:
       printf("Enter a number (0-20)\n");
        number = UserInput(MIN_NUMBER, MAX_NUMBER);

    Code:
    int UserInput(int min, int max){
    
        int input;    
        input = 0;
    
        printf(CURSOR);
    
        while (scanf("%d",&input) != 1 || input < min || input > max){
            while (getchar() != '\n');
            printf(CURSOR);
        }
    
        // while (getchar() != '\n'); <<<--- Should I put this here??
    
        return input;
    }

    My question is two fold. First, shouldn't I clear the stdin before leaving this function. From what I can tell it still leaves junk in the buffer.

    Two what would happen if the user exceeds the buffer? I tried it and seems the function works fine no matter how large a number is entered.

  2. #2
    Registered User jimtuv's Avatar
    Join Date
    May 2010
    Location
    Sylvania, Ohio
    Posts
    94
    I have made a test code to tell what is left in the buffer

    Code:
    #include <stdio.h>
    
    
    #define MIN_NUMBER 0
    #define MAX_NUMBER 20
    #define CURSOR ">>>"
    
    
    int UserInput(int Min, int Max);
    
    
    int main(void){
        
    	
        int number;
        
        char name [BUFSIZ];
    
            
        
        printf("Enter a number (0-20)\n");
        number = UserInput(MIN_NUMBER, MAX_NUMBER);
        printf("this is outside the function\n");
        fgets(name, BUFSIZ, stdin);
    
        
        printf("number you entered = %d\n",number);
        printf("This is junk left %s",name);
    
    
    	
        return 0;
    }
    
    int UserInput(int min, int max){
    
        int input;    
        input = 0;
    
        printf(CURSOR);
    
        while (scanf("%d",&input) != 1 || input < min || input > max){
            while (getchar() != '\n');
            printf(CURSOR);
        }
    
        //while (getchar() != '\n');
        
        return input;
    }
    The output looks like this

    Enter a number (0-20)
    >>>12 this is junk left in the buffer
    this is outside the function
    number you entered = 12
    This is junk left this is junk left in the buffer
    Press [Enter] to close the terminal ...

    I do not get a prompt for more input it just goes right to the print.

    Now I uncomment that while(getchar() != '\n'); and this is the output

    Enter a number (0-20)
    >>>12 this is junk left in the buffer
    this is outside the function
    no junk this time
    number you entered = 12
    This is junk left no junk this time
    Press [Enter] to close the terminal ...

    Now I do get a prompt for input it does not go straight to the print.
    It waits for input at the fgets(name, BUFSIZ, stdin);


    As you can see it does leave stuff in the buffer so I guess I answered that question. But I am still unclear what happens in a buffer overflow.
    Last edited by jimtuv; 06-05-2010 at 05:40 PM.

  3. #3
    ATH0 quzah's Avatar
    Join Date
    Oct 2001
    Posts
    14,826
    It's not buffer overflow, that's something else entirely. If I ask you to enter a number on your keyboard via some console application, as you are doing here, what really happens?

    1<enter>

    That's two keystrokes. You read the number, and the <enter> is hanging around waiting for you to do something with it. Different methods of reading handle the buffer differently. Some of them are non-buffered, like this edit box in the browser for example, and as soon as you hit a key, it sees I just pressed d and puts a d on my screen. Some of them are line buffered, meaning it waits until a specific event happens, before it reads the input. This event is typically the newline character (enter) being hit, hence the term 'line buffered'.

    So basically, you just need to remember that most every keystroke you hit (for the most part, ALT, CTRL, etc., aren't generally registered the same way) needs to be handled appropriately. I.e., you hit enter, so you need to do something to catch that enter key that you've entered, and do something with it. A call to getchar, simply reads the next available key that is in the buffer, and returns it (which here is just ignored).


    Quzah.
    Hope is the first step on the road to disappointment.

  4. #4
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    You cannot get a buffer overflow because your code is safe. What happens if the user types more characters than you have room for is simply that fgets returns having read BUFISZ characters and leaves the rest in the buffer. Next time you read, it fetches BUFSIZ more, and so on.
    Now, if you changed that constant not to match the true buffer size, you may get a buffer overflow. If you're curious about what a buffer overflow can do, look no further than SourceForge.net: Buffer overrun - cpwiki
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  5. #5
    Registered User jimtuv's Avatar
    Join Date
    May 2010
    Location
    Sylvania, Ohio
    Posts
    94
    Thank you so much Elysia and quzah! The info is helping me understand whats happening. I sure do appreciate the people who contributed to the FAQ as well. It is an awesome tool! I have learned so much from it. I still only understand the basic information there as I am just starting to learn. That's why I have been exploring the example code given there. It sure helps to reproduce it and play with the code to see what everything does.

    Input is a very complex subject. I can see it will take a while to learn all the intricacies. I can see why the tutorials just assume the user will input the correct data (not that I agree with the practice). Trying to bullet proof user input takes a deeper understanding then a beginner is capable of. Though it seems it would be a good idea when teaching to show the correct way to code user input and just state that it needs to be done this way and an explanation will come later when it will be understood. Instead of just assuming that users will input flawlessly.

    My plans are to understand and build up from the least complex

    single digit integer input
    multi digit integer input
    floating point numbers
    alpha characters to strings
    mix number and strings

    I hope in that way I can get a feel for the complexities one step at a time.

    I had been avoiding trying out the code that was considered the wrong way to do it. But I have found it informative to test out bad code and see why it fails. I have actually learned more from this approach then just memorizing and testing the recommended code.

  6. #6
    Registered User jimtuv's Avatar
    Join Date
    May 2010
    Location
    Sylvania, Ohio
    Posts
    94
    Ok I went through and made a new example of a integer number input using a custom function which is modified from the last FAQ example code. I added it to the bottom of my other examples to compare them. It is interesting that the custom function is the only one of the three that does not fail in some part. I see now how much better it is to use fgets into a string because of the ability to check the entire string for correctness before converting the string into a int. The great part is I don't have to worry about junk in the stdin left over after the call. The only drawback I have right now is if the user hits the enter key without typing anything it returns 0 instead of calling that an error and waiting for more input.

    Here is my code with output at the end

    Code:
    /* 
     * File:   numbinput.c
     * Author: jim
     *
     * Created on June 5, 2010, 11:50 AM
     */
    
    /* *******************************************************
    * Testbed for number input routines
    *************************************************************/
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <ctype.h>
    #include <string.h>
    
    #define MIN_NUMBER 0
    #define MAX_NUMBER 20
    #define MIN_FLOAT 0.01
    #define MAX_FLOAT 20.00
    #define MIN_SNUMBER 0
    #define MAX_SNUMBER 50
    #define CURSOR ">>>"
    
    int UserInput(int min, int max);
    double UserInputFloat(double min, double max);
    int UserInputStrgNumb(int min, int max);
    int Validate(char *a);
    
    int main(void){
        
    	
        int number;
        double fnumber;
        int snumber;        
        
        printf("Enter a number for UserInput (0-20)\n");
        number = UserInput(MIN_NUMBER, MAX_NUMBER);
    
        printf("Enter number for UserInputFloat (0.00-20.00)\n");
        fnumber = UserInputFloat(MIN_FLOAT, MAX_FLOAT);
    
        printf("Enter a number for UserInputStrgNumb (0-50)\n");
        snumber = UserInputStrgNumb(MIN_SNUMBER, MAX_SNUMBER);   
            
        printf("The number you entered for UserInput = %d\n",number);
        printf("The number you entered for UserInputFloat = %.2f\n",fnumber);
        printf("The number you entered for UserInputStrgNumb = %d\n",snumber);
    	
        return 0;
    }
    /***********************************
     * UserInput
     * Parameters:
     * min:         minimum number to except
     * max:         maximum number to except
     * Returns:     integer number in the range (min,max)
     * Prints:      nothing
     ***********************************/
    int UserInput(int min, int max){
    
        int input;   
        input = 0;
    
        printf(CURSOR);
        
        while (scanf("%d",&input) != 1 || input < min || input > max){
            while (getchar() != '\n'); 
            printf(CURSOR);
        }
    
        while (getchar() != '\n'); 
                                    
        
        return input;
    }
    /***********************************
     * UserInputFloat
     * Parameters:
     * min:         minimum number to except
     * max:         maximum number to except
     * Returns:     double floating point number in the range (min,max)
     * Prints:      nothing
     ***********************************/
    double UserInputFloat(double min, double max){
    
        double input;
        input = 0.0;
    
        printf(CURSOR);
    
        while (scanf("%lf",&input) != 1 || input < min || input > max ){
            while (getchar() != '\n');
            printf(CURSOR);
        }
    
        while (getchar() != '\n');
    
        return input;
    }
    /***********************************
     * UserInputStrgNumb
     * Parameters:
     * min:         minimum number to except
     * max:         maximum number to except
     * Returns:     integer number in the range (min,max)
     * Prints:      nothing
     ***********************************/
    int UserInputStrgNumb(int min, int max){
    
        int i;
        int error = 1;
        char buffer[BUFSIZ]; 
    
        printf(CURSOR);
    
        while(error == 1 || error == 2 || i < min || i > max){
            if (fgets(buffer, sizeof buffer, stdin) != NULL) {
                 buffer[strlen(buffer) - 1 ] = '\0';
                 if (Validate(buffer) == 0){
                     i = atoi(buffer);
                     error = 0;
                     if ( i < min || i > max){
                         printf(CURSOR);
                     }
                 }
                 else{
                     error = 2;
                     printf(CURSOR);
                 }
            }
            else{
                error = 1;
                printf(CURSOR);
            }
        }
        return i;
    }
    /***********************************
     * Validate
     * Parameters:
     * char *a:     is a pointer to the string containing the possible number
     * Returns:     int error code (o for good number and 1 for bad number)
     * Prints:      nothing
     ***********************************/
    
    int Validate(char *a){
    
        unsigned x;
        
        for (x = 0; x < strlen ( a ); x++){
            if (!isdigit(a[x])) return 1;
        }
        return 0;
    }
    
    /*
     *  OUTPUT
    
    Enter a number for UserInput (0-20)
    >>>a
    >>>a1
    >>>-1
    >>>22
    >>>12d
    Enter number for UserInputFloat (0.00-20.00)
    >>>-1.0
    >>>0.003
    >>>21.5
    >>>a12.5
    >>>1a2.5
    Enter a number for UserInputStrgNumb (0-50)
    >>>-1
    >>>51
    >>>a51
    >>>5a1
    >>>51a
    >>>25a
    >>>a25
    >>>2a5
    >>>25
    The number you entered for UserInput = 12
    The number you entered for UserInputFloat = 1.00
    The number you entered for UserInputStrgNumb = 25
    Press [Enter] to close the terminal ...
    
     */

  7. #7
    Registered User jimtuv's Avatar
    Join Date
    May 2010
    Location
    Sylvania, Ohio
    Posts
    94
    fixed the UserInputStrgNumber functions it was as simple as adding a check for '\0' at the beginning of the buffer array and sticking in another printf(CURSOR);

    so here is the fixed code:
    Code:
    int UserInputStrgNumb(int min, int max){
    
        int i;
        int error = 1;
        char buffer[BUFSIZ]; 
    
        printf(CURSOR);
    
        while(error == 1 || error == 2 || i < min || i > max || buffer[0] == '\0'){
            if (fgets(buffer, sizeof buffer, stdin) != NULL) {
                 buffer[strlen(buffer) - 1 ] = '\0';
                 if (Validate(buffer) == 0){
                     i = atoi(buffer);
                     error = 0;
                     if ( i < min || i > max){
                         printf(CURSOR);
                     }
                 }
                 else{
                     error = 2;
                     printf(CURSOR);
                 }
            }
            else{
                error = 1;
                printf(CURSOR);
            }
            
          printf(CURSOR);
        }
       return i;
    }

  8. #8
    Registered User
    Join Date
    May 2010
    Location
    Naypyidaw
    Posts
    1,314
    UserInputStrgNumb() is not accepting negative numbers.

  9. #9
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    strtol is not complex. It's very easy, actually.
    Code:
    const char* number = "-100";
    const char* pEnd = NULL;
    long l = strtol(number, &pEnd, 10);
    if (pEnd == number)
        ; // Operation failed; number does not contain a number.
    If it succeeds, l contains your number.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  10. #10
    Lurking whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,612
    If it doesn't errno should report what happened as well.

  11. #11
    Registered User jimtuv's Avatar
    Join Date
    May 2010
    Location
    Sylvania, Ohio
    Posts
    94
    So using atoi and validating the input using isdigit the way I did is a mistake??? I can see that strtol is great for conversions from other bases but what is the advantage here?? And how do I handle the validation.

    I am totally unfirmilliar with errno though if I remember I read something about that a few days ago. I would have to #include <errno.h> and then on a failure return code from strol check the value that is in errno for what type of error it was correct? This would be only necessary if I were going to do something specific after an error. Otherwise I could just force reentry of the data on an error from strol and then checking errno would be unnecessary.

  12. #12
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Quote Originally Posted by jimtuv View Post
    So using atoi and validating the input using isdigit the way I did is a mistake??? I can see that strtol is great for conversions from other bases but what is the advantage here?? And how do I handle the validation.
    atoi doesn't handle underflow/overflow, among others.
    strtol contains sophisticated and powerful parsing of numbers. Probably better than your own. Plus it's only a few lines. Much fewer than your solution. I'd say those are the advantages.

    I am totally unfirmilliar with errno though if I remember I read something about that a few days ago. I would have to #include <errno.h> and then on a failure return code from strol check the value that is in errno for what type of error it was correct? This would be only necessary if I were going to do something specific after an error. Otherwise I could just force reentry of the data on an error from strol and then checking errno would be unnecessary.
    That pretty much sums it up.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  13. #13
    Registered User jimtuv's Avatar
    Join Date
    May 2010
    Location
    Sylvania, Ohio
    Posts
    94
    Ok here is my try at this. I think I understand what is going on pretty well. It seems to work and your right it's much smaller. I can dump the Validate function completely. I hope this is the right way to do this.



    Code:
    long int InputNext(int min, int max){
    
        char buffer[BUFSIZ];    
        char* pEnd;
        long int input;
    
        errno = 0;
       
        printf(CURSOR);
    
        while(errno != 42 ||
                errno == ERANGE ||
                *pEnd != '\0' ||
                input == 0 && buffer[0] != '0')
        {
           if ( fgets(buffer, sizeof buffer, stdin) != NULL ){
               buffer[strlen(buffer) -1] = '\0';
               input = strtol(buffer,&pEnd,10);            
           }
           printf(CURSOR);
        }
        return input;
    }

  14. #14
    Lurking whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,612
    If it works, so much the better, but people probably wont know what 42, the meaning of life, the universe, and everything, has to do with numerical input. (It actually corresponds to an illegal byte sequence in MinGW32, now that I look it up, but I can't say that's relevant. EINVAL is the only other err code besides ERANGE that matters.) Not to mention you de-reference pEnd before it points somewhere.

    I don't know how long you've been coding, but you can stick to any line reading convention that you know. It's a lot easier to define the loop in terms of success as well.

    Code:
    while(fgets(buffer, sizeof buffer, stdin) != NULL)
    {
       errno = 0;
       input = strtol(buffer, &pEnd, 10);
       if(*pEnd == '\n' && errno == 0) 
       {
          break;
       }
    }
    Not that you can't fix what you have.

  15. #15
    Registered User jimtuv's Avatar
    Join Date
    May 2010
    Location
    Sylvania, Ohio
    Posts
    94
    LOL When I realized it returned 42 I chuckled a bit thinking Dennis Ritchie might have stuck a "Hitch-hiker's Guide to the Galaxy" reference in.

    I am pretty green I started learning C on 05/01/2010 so only been at it a little while. I am going pretty slowly to insure I understand before moving on.

    Not to mention you de-reference pEnd before it points somewhere.
    Ooops! I was caught up in making the condition and didn't even notice that. I will need to be more careful and write out the algorithms first rather then building things trial and error as I have been doing.

    It's a lot easier to define the loop in terms of success as well.
    I had actually considered this first but went with the second guess. I should listen to my instincts more often.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. How to limit user input to a certain number of digits?
    By NewbGuy in forum C Programming
    Replies: 7
    Last Post: 05-08-2009, 09:57 PM
  2. Program that requests input of an integer number
    By theejuice in forum C Programming
    Replies: 6
    Last Post: 04-30-2008, 02:18 AM
  3. Input statement problem
    By une in forum C Programming
    Replies: 3
    Last Post: 05-29-2007, 11:16 PM
  4. Prevent user input
    By bonne_tim in forum C Programming
    Replies: 11
    Last Post: 01-01-2007, 03:18 PM
  5. ~ User Input script help~
    By indy in forum C Programming
    Replies: 4
    Last Post: 12-02-2003, 06:01 AM