简体   繁体   中英

scanf c wrong input

Good afternoon, my question is conceptual. How can I make it generate a "fancy" error when the user incorrectly enters some data that does not correspond to the scanf() function? So as to only allow integers to be entered in the example below (not characters or array of characters or an inappropriate data).

For example:

#include <stdio.h>

int a;
printf("Enter a number\n");
scanf("%d", &a); //the user is supposed to enter a number
printf("Your number is %d ", a);

//but if the user enters something inappropriate, like a character, the program leads to 
//undetermined behavior (which as I understand it interprets said character according to its 
//value in the ASCII code).

From already thank you very much

How to get verified user input of a specific type

#1 Get user input as a string

char s[100];
if (!fgets( s, sizeof(s), stdin )) *s = '\0';
char * p = strptok( s, "\r\n" );
if (!p) complain_and_quit();
*p = '\0';

...

Alternately:

#define __STDC_WANT_LIB_EXT2__ 1
#include <stdio.h>

char * s = NULL;
size_t n = 0;
if (getline( &s, &n, stdin ) < 0)
{
  free( s );
  complain_and_quit();
}

...

free( s );

#2 Try to convert that string to the type of thing you want.

char * p;
int user_input = strtol( s, &p, 10 );
if (*p)
{
  // Input was not JUST an integer.
  // It could be something like "123 xyz", or "not-an-integer".
  // Look at the value of p to figure out where the conversion went wrong.
  complain();
}

do_something_with_an_integer( user_input );

That's it!

The best option is to use fgets to parse the input, it reads the input stream as a string, which gives you more options to deal with bad input, and you already have some suggestions on how to do it. In any case, if you are adamant in using scanf , you could do something like this:

//...
int a;
printf("Enter a number\n");
while (scanf("%d", &a) == 0) //while input is not correctly parsed...
{
    fprintf(stderr, "ERROR, non numeric value is not allowed\n"); //...print error message...
    int c;
    while((c = getchar()) != '\n' && c != EOF) {} //...and clear stdin
}
printf("Your number is %d ", a);
//...

The above code will keep prompting the user to get an input until a valid one is provided.

In order to determine whether scanf was able to successfully convert the input to an integer, you should check the return value of scanf :

#include <stdio.h>
#include <stdlib.h>

int main( void )
{
    int num;

    printf( "Enter a number: " );
    if ( scanf( "%d", &num ) != 1 )
    {
        printf( "Failed to convert input!\n" );
        exit( EXIT_FAILURE );
    }

    printf( "Conversion successful! The number is %d.\n", num );
}

However, using scanf for line-based user input is generally not recommended, because scanf does not behave in an intuitive manner when dealing with that kind of input. For example, scanf will generally not consume an entire line of input at once. Instead, it will generally only consume the input that matches the argument, but will leave the rest of the line on the input stream, including the newline character.

Leaving the newline character on the input stream can already cause a lot of trouble. For example, see this question .

Also, if the user enters for example 6abc , then scanf will successfully match the 6 and report success, but leave abc on the input stream, so that the next call to scanf will probably immediately fail.

For this reason, it is generally better to always read one line of input at a time, using the function fgets . After successfully reading one line of input as a string, you can use the function strtol to attempt to convert the string to an integer:

#include <stdio.h>
#include <stdlib.h>

int main( void )
{
    char line[200], *p;
    int num;

    //prompt user for input
    printf( "Enter a number: " );

    //attempt to read one line of input
    if ( fgets( line, sizeof line, stdin ) == NULL )
    {
        printf( "Input failure!\n" );
        exit( EXIT_FAILURE );
    }

    //attempt to convert strint to integer
    num = strtol( line, &p, 10 );
    if ( p == line )
    {
        printf( "Unable to convert to integer!\n" );
        exit( EXIT_FAILURE );
    }

    //print result
    printf( "Conversion successful! The number is %d.\n", num );
}

However, this code has the following issues:

  1. It does not check whether the input line was too long to fit into the buffer.

  2. It does not check whether the converted number is representable as an int , ie whether the value the user entered is too large to be stored in an int .

  3. It will accept 6abc as valid input for the number 6 . This is not as bad as scanf , because scanf will leave abc on the input stream, whereas fgets will not. However, it would probably still be better to reject the input instead of accepting it.

Here is an improved version of the code, which solves the issues mentioned above and also puts everything into a function. This function will reprompt the user for input, until the input is valid.

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

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;
    }
}

int main( void )
{
    int number;

    number = get_int_from_user( "Enter a number: " );

    printf( "Input was valid.\n" );
    printf( "The number is: %d\n", number );

    return 0;
}

This program has the following behavior:

Enter a number: abc
Error converting string to number!
Enter a number: 6000000000
Number out of range error!
Enter a number: 6 7 8
Unexpected input encountered!
Enter a number: 6abc
Unexpected input encountered!
Enter a number: 6
Input was valid.
The number is: 6

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM