2

I have a problem because my code isn't working when I do this:

#include <stdio.h>

int main() {
  int age;

  printf("Enter your age: ");
  scanf("%d", &age);

  while(age == (what do i put here)) {
    printf("\nYou have missed something\n");
            
    printf("Enter your age: ");
    scanf("%d", &age);
  }
}
user16217248
  • 3,119
  • 19
  • 19
  • 37
yeh
  • 21
  • 2
  • 4
    look at the return value of scanf https://man7.org/linux/man-pages/man3/scanf.3.html – pm100 Apr 05 '23 at 04:14
  • also look at this https://medium.com/@pm100/common-stackoverflow-errors-2-scanf-ec6907d76f68 – pm100 Apr 05 '23 at 04:15
  • The user **has** put some input, because *the program will not continue running* until the user presses the Enter key. The question is whether that input is *valid*. – Karl Knechtel Apr 05 '23 at 05:34
  • 1
    @KarlKnechtel A little nitpick: `scanf` may also return on "input failure" – Support Ukraine Apr 05 '23 at 06:00
  • @yacc: In my opinion, your proposed duplicate question is not a duplicate. Although the solution may be the same in both questions, your proposed duplicate question focusses on the issue of rejecting certain input even in cases in which `scanf` succeeded, by examining the remainder of the line that was not matched by `scanf`. That issue is not addressed in the question at hand (although I do also address that issue in my answers to both questions). – Andreas Wenzel Apr 05 '23 at 19:12

2 Answers2

1

How to know if a user had not put any input?

The short answer is: By checking the scanf return value

On success scanf returns the number of items matched. On error (before first successful match) scanf returns EOF.

In code this would be:

int items_scanned = scanf("%d", &age);
if (items_scanned == EOF)
{
    // Input error. Could not read any input
    // Add error handling
}
else if (items_scanned == 1)
{
    // All good. Scanned one item, i.e. age now contains an integer
    printf("Age is %d\n", age);
}
else
{
    // The input did not represent a valid integer
    // Add error handling
}

This raises the question: What to do for the "error handling" part?

When scanf returns EOF something real bad has happened. One can read errno and maybe get more information and an idea how to recover. It's rather complex. For simple programs one could just terminate by calling exit.

When scanf returns a value different from what you expect (here: 1) it's because the next input character can't be matched. For instance the user input is a non-digit character like an a, b, etc. The simplest error handling is to read that character using getchar and then try scanf again. Another approach would be to keep reading and discarding characters until a newline is found. It's a design decision...

And further: How to keep reading until age is a valid integer?

Use some kind of loop. It can be done in many different ways. Below is a very simple approach.

while(1)
{
    int items_scanned = scanf("%d", &age);
    if (items_scanned == EOF)
    {
        // Input error. Could not read any input
        // Add error handling
        exit(1);
    }
    else if (items_scanned == 1)
    {
        // All good. Scanned one item, i.e. age now contains an integer
        // Stop the loop
        break;
    }
    else
    {
        // The input did not represent a valid integer
        // Add error handling
        // Remove the next input character
        int tmp = getchar();
        if (tmp == EOF)
        {
            // Input error. Could not read any input
            // Add error handling
            exit(1);
        }
    }
}
// When arriving here, age is a valid integer from the user
printf("Age is %d\n", age);

That said...

Taking user input is not as easy as it may sound. Before writing any code, some design work is needed to specify what kind of input that the program shall consider valid.

The small program above will for instance accept "abc12" as the integer value 12. The program designer must decide if that's acceptable.

Likewise, input like "3.14" as the integer value 3. The program designer must decide if that's acceptable.

scanf with %d will itself accept input like "42abc" as the integer value 42. The program designer must decide if that's acceptable.

And what you expect/accept if the user input is an integer larger than an int can hold? Like "1234567890123456789012345678901234567890"

The designers of scanf made their choice. The way scanf is used also adds a parsing choice. As program designer you need to decide what is acceptable for your program. It may turn out that scanf isn't the correct tool. You may want to look at fgets, sscanf, strtol, etc.

Final note: The code above also allows age to be negative. Perhaps consider using an unsigned int for age and %u in scanf

Support Ukraine
  • 42,271
  • 4
  • 38
  • 63
  • In this case, separating into three cases is the way to go; it will accept eg, EOF gracefully by `exit` _vs_ a matching error which would get you a hung programme. +1 – Neil Apr 05 '23 at 19:57
0

Doing something like

while(age == (put something here)) {

would not make sense, because if scanf fails due to the user entering invalid input, then the variable age will have an indeterminate value.

Instead of checking the value of age, you should check the return value of scanf to determine whether the conversion was successful, for example like this:

#include <stdio.h>

int main( void )
{
    int age;

    printf( "Enter your age: " );

    if ( scanf( "%d", &age ) == 1 )
    {
        printf( "Input was valid! It was %d.\n", age );
    }
    else
    {
        printf( "Input was invalid!\n" );
    }
}

If you want to do this in a loop which repeats until the input is valid, then you could use this code:

#include <stdio.h>

int main( void )
{
    int age;

    //repeat until input is valid
    for (;;) //infinite loop, equivalent to while(1)
    {
        printf( "Enter your age: " );

        if ( scanf( "%d", &age ) != 1 )
        {
            int c;

            printf( "Invalid input! Try again.\n" );

            //discard remainder of line
            do
            {
                c = getchar();

            } while ( c != EOF && c != '\n' );

            continue;
        }

        //input was ok, so break out of the infinite loop
        break;
    }

    printf( "The input was successfully converted to %d.\n", age );
}

This program has the following behavior:

Enter your age: abc
Invalid input! Try again.
Enter your age: 25
The input was successfully converted to 25.

However, this solution is not perfect, as it accepts the input 25abc as valid input for the number 25:

Enter your age: 25abc
The input was successfully converted to 25.

In this case, it would probably be better to instead reject the input and to prompt the user to enter new input.

Another issue is that scanf will not print an error message and will not reprompt the user if the user enters an empty line, but will instead continue reading input until a non-whitespace character is entered:

Enter your age: 


abc
Invalid input! Try again.
Enter your age: 


25
The input was successfully converted to 25.

This is because scanf will consume and discard all whitespace characters when the %d specifier is used.

When dealing with line-based user input, it is usually better not to use scanf. Instead, I recommend to always read one entire line of input as a string using the function fgets and then to attempt to convert that string to an integer using the functon strtol.

Here is an example program which fixes all of the issues mentioned above, and adds some additional input validation:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <errno.h>

//forward declaration
int get_int_from_user( const char *prompt );

int main( void )
{
    int age;

    age = get_int_from_user( "Enter your age: " );

    printf( "The input was successfully converted to %d.\n", age );
}

//This function will attempt to read one integer from the user. If
//the input is invalid, it will automatically reprompt the user,
//until the input is valid.
int get_int_from_user( const char *prompt )
{
    //loop forever until user enters a valid number
    for (;;)
    {
        char buffer[1024], *p;
        long l;

        //prompt user for input
        fputs( prompt, stdout );

        //get one line of input from input stream
        if ( fgets( buffer, sizeof buffer, stdin ) == NULL )
        {
            fprintf( stderr, "Unrecoverable input error!\n" );
            exit( EXIT_FAILURE );
        }

        //make sure that entire line was read in (i.e. that
        //the buffer was not too small)
        if ( strchr( buffer, '\n' ) == NULL && !feof( stdin ) )
        {
            int c;

            printf( "Line input was too long!\n" );

            //discard remainder of line
            do
            {
                c = getchar();

                if ( c == EOF )
                {
                    fprintf( stderr, "Unrecoverable error reading from input!\n" );
                    exit( EXIT_FAILURE );
                }

            } while ( c != '\n' );

            continue;
        }

        //attempt to convert string to number
        errno = 0;
        l = strtol( buffer, &p, 10 );
        if ( p == buffer )
        {
            printf( "Error converting string to number!\n" );
            continue;
        }

        //make sure that number is representable as an "int"
        if ( errno == ERANGE || l < INT_MIN || l > INT_MAX )
        {
            printf( "Number out of range error!\n" );
            continue;
        }

        //make sure that remainder of line contains only whitespace,
        //so that input such as "6abc" gets rejected
        for ( ; *p != '\0'; p++ )
        {
            if ( !isspace( (unsigned char)*p ) )
            {
                printf( "Unexpected input encountered!\n" );

                //cannot use `continue` here, because that would go to
                //the next iteration of the innermost loop, but we
                //want to go to the next iteration of the outer loop
                goto continue_outer_loop;
            }
        }

        return l;

    continue_outer_loop:
        continue;
    }
}

This program has the following behavior:

Enter your age: 
Error converting string to number!
Enter your age: test
Error converting string to number!
Enter your age: 25abc
Unexpected input encountered!
Enter your age: 25
The input was successfully converted to 25.
Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
  • 1
    Is there seriously not a canonical duplicate for this? – Karl Knechtel Apr 05 '23 at 05:35
  • @KarlKnechtel Maybe.... but someone needs to find it. This question currently have 2 close votes stating a dupe that asks another question. It's true that the answer for this question can be found if the dupe is examined carefully but it's still two different questions. – Support Ukraine Apr 05 '23 at 05:55
  • @KarlKnechtel: The closest possible dupe that I am aware of is [this question](https://stackoverflow.com/q/31633005/12149471), which contains a similar answer of mine. However, that question focusses on the issue of discarding the remainder of a line between two calls to `scanf` to prevent an infinite loop, whereas the question at hand does not directly ask about that problem, but instead focusses on the issue of determining whether `scanf` succeeded or not. – Andreas Wenzel Apr 05 '23 at 06:15
  • Wow, seriously? You [tag:c] folks don't have canonicals for even the simplest things that I'd expect beginners to ask about constantly, apparently. I thought the Python canonicals were in a sorry state, but at least we can point at e.g. https://stackoverflow.com/questions/20449427 or https://stackoverflow.com/questions/23294658 several times a day if necessary. – Karl Knechtel Apr 05 '23 at 06:24
  • Or maybe you do. I pretty easily found https://stackoverflow.com/questions/4072190, and the close votes are for https://stackoverflow.com/questions/26583717 and I *don't honestly see how it's a different question*. – Karl Knechtel Apr 05 '23 at 06:27
  • @KarlKnechtel: Regarding the first link you posted in your previous comment, I don't think that it is a duplicate, because in the question at hand, OP is attempting to re-prompt the user for input if the input is invalid. This raises the additional issue of discarding the invalid input, in order to prevent an infinite loop. – Andreas Wenzel Apr 05 '23 at 06:34
  • @KarlKnechtel: Regarding the second link you posted in your previous comment, that question focusses on the issue of rejecting certain input even in cases in which `scanf` succeeded, by examining the remainder of the line that was not matched by `scanf`. That issue is not addressed in the question at hand (although I do also address that issue in my answer). – Andreas Wenzel Apr 05 '23 at 06:39
  • 1
    Whoah, @Karl, chill. Our canonical duplicate is ["What can I use for input conversion instead of `scanf`?"](https://stackoverflow.com/questions/58403537), but that tells you how to do input without using the benighted `scanf` function at all. Beginners, however, are obligated (by a dysfunctional educational system, that is) *to* use `scanf`, so we pretty much need to give them all individual handholding like this. – Steve Summit Apr 05 '23 at 18:23
  • 1
    @SteveSummit: `"Beginners, however, are obligated (by a dysfunctional educational system, that is) to use scanf"` -- I fully agree. However, one notable example which does not do this is the CS50 course. Instead, that course provides its own helper functions which its students are supposed to use for input, such as [`get_int`](https://manual.cs50.io/3/get_int), `get_double`, `get_string`, etc. The CS50 function `get_int` is very similar to my function `get_int_from_user` that I am using in my answer above. – Andreas Wenzel Apr 05 '23 at 18:52
  • @SteveSummit: Unfortunately, the CS50 course has a bad reputation on Stack Overflow, because in its first three courses, it hides the data type `char *` behind `typedef char *string;`, in order to allow its students to use strings (in particular the input function [`get_string`](https://manual.cs50.io/3/get_string)) before they learn about pointers. – Andreas Wenzel Apr 05 '23 at 18:55
  • @AndreasWenzel Indeed. There is much to complain about when it comes to CS50, but those "easier" input functions they're trying to promulgate are pure gold. – Steve Summit Apr 05 '23 at 18:55
  • Given that beginners will use (and educators will teach) `scanf` regardless, a canonical answer using `scanf` belongs on the canonical question, then. – Karl Knechtel Apr 06 '23 at 05:59