Although it is generally not recommended to use scanf
for line-based user input, I will first present a solution which does use scanf
.
This solution works by checking the remaining characters on the line that were not consumed by scanf
. Generally, this should only be the newline character. However, since using the %d
specifier with scanf
accepts leading whitespace characters, it would be consistent if the program also accepted trailing whitespace characters.
Here is my solution which uses scanf
:
#include <stdio.h>
#include <ctype.h>
int main( void )
{
int n;
//repeat until input is valid
for (;;) //infinite loop, equivalent to while(1)
{
int c;
//prompt user for input
printf( "Please enter an integer: " );
//attempt to read and convert input
if ( scanf( "%d", &n ) == 1 )
{
//verify that remainder of input only consists of
//whitespace characters
while ( ( c = getchar() ) != EOF && c != '\n' )
{
if ( !isspace(c) )
{
//we cannot use "break" here, because we want
//to break out of the outer loop, not the inner
//loop
goto invalid_input;
}
}
//input is valid
break;
}
invalid_input:
//print error message
printf( "Input is invalid!\n" );
//discard remainder of line
do
{
c = getchar();
} while ( c != EOF && c != '\n' );
}
printf("You entered: %d\n",n);
return 0;
}
This program has the following behavior:
Please enter an integer: abc
Input is invalid!
Please enter an integer: 6abc
Input is invalid!
Please enter an integer: 6.7
Input is invalid!
Please enter an integer: 6
You entered: 6
This scanf
solution has the following issues:
- The program will have undefined behavior if the user enters a number that is not representable as an
int
(for example a number that is larger than INT_MAX
).
- If the user enters an empty line, it does not print an error message. This is because the
%d
specifier of scanf
consumes all leading whitespace characters.
These two issues can be solved by using the functions fgets
and strtol
, instead of the function scanf
.
Performing all of these validation checks makes the code quite large, though. Therefore, it would make sense to put all of the code into its own function. Here is an example which uses the function get_int_from_user
, which I took from this answer of mine to another question:
#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 );
int main( void )
{
int number;
number = get_int_from_user( "Please enter an integer: " );
printf( "Input was valid.\n" );
printf( "The number is: %d\n", number );
return 0;
}
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 "6sdfj23jlj" 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:
Please enter an integer: abc
Error converting string to number!
Please enter an integer: 6abc
Unexpected input encountered!
Please enter an integer: 6.7
Unexpected input encountered!
Please enter an integer: 6000000000
Number out of range error!
Please enter an integer: 6
Input was valid.
The number is: 6