Input validation using scanf()
You should use scanf
return value. From man scanf
:
Return Value
These functions return the number of input items successfully matched and assigned, which can be fewer than provided for, or even zero in the event of an early matching failure.
So it may look like this:
if (scanf("%d", &num) != 1)
{
/* Display error message. */
}
Notice it doesn't work for "numbers with the decimal point". For this you should rather use parsing and strtol
for instance. It may be a bit more complex.
How to validate input using scanf
Using scanf()
is usually a bad idea for user input since failure leaves the FILE
pointer at an unknown position. That's because scanf
stands for "scan formatted" and there is little more unformatted than user input.
I would suggest using fgets()
to get a line in, followed by sscanf()
on the string to actually check and process it.
This also allows you to check the string for those characters you desire (either via a loop or with a regular expression), something which the scanf
family of functions is not really suited for.
By way of example, using scanf()
with a "%d"
or "%f"
will stop at the first non-number character so won't catch trailing errors like "127hello"
, which will just give you 127. And using it with a non-bounded %s
is just begging for a buffer overflow.
If you really must use the []
format specifier (in scanf
or sscanf
), I don't think it's meant to be followed by s
.
And, for a robust input solution using that advice, see here. Once you have an input line as a string, you can sscanf
to your hearts content.
int validation using scanf and isdigit
Don't use isdigit()
— it is for checking whether a character is a digit or not. You've read a number. You must check the return value from scanf()
— if it is not 1, you've got a problem. Depending on your requirements, the fact that there may be all sorts of stuff on the line after the number may or may not be a problem. I'm assuming when you say "validate the input is an integer", you want to allow for multiple-digit numbers, and negative numbers, and since you used %i
rather than %d
, you're fine with octal values (leading 0) or hexadecimal values (leading 0x or 0X) being entered too.
Note that if you have:
int checker = scanf("%i", &i);
then the result could be 1
, 0
, or EOF
. If the result is 1
, then you got an integer after possible leading white space, including possibly multiple newlines. There could be all sorts of 'garbage' after the number and before the next newline. If the result is 0
, then after skipping possible white space, including possibly multiple newlines, the first character that wasn't white space also wasn't part of an integer (or it might have been a sign not immediately followed by a digit). If the result is EOF
, then end-of-file was detected after possibly reading white space, possibly including multiple newlines, but before anything other than white space was read.
To continue sensibly, you need to check that the value returned was 1
. Even then, there could be problems if the value is out of the valid range for the int
type.
The full requirement isn't completely clear yet. However, I'm going to assume that the user is required to enter a number on the first line of input, with possibly a sign (-
or +
), and possibly in octal (leading 0) or hexadecimal (leading 0x or 0X), and with at most white space after the number and before the newline. And that the value must be in the range INT_MIN
.. INT_MAX
? (The behaviour of scanf()
on overflow is undefined — just to add to your woes.)
The correct tools to use for this are fgets()
or POSIXgetline()
to read the line, andstrtol()
to convert to a number, and isspace()
to validate the tail end of the line.
Note that using strtol()
properly is quite tricky.
In the code below, note the cast to unsigned char
in the call to isspace()
. That ensures that a valid value is passed to the function, even if the plain char
type is signed and the character entered by the user has the high bit set so the value would convert to a negative integer. The valid inputs for any of the ispqrst()
or topqrst()
functions are EOF
or the range of unsigned char
.
C11 §7.4 Character handling <ctype.h>
¶1
… In all cases the argument is an
int
, the value of which shall be representable as anunsigned char
or shall equal the value of the macroEOF
. If the argument has any other value, the behavior is undefined.
The GNU C library tends to protect the careless, but you should not rely on being nannied by your standard C library.
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char line[4096]; /* Make it bigger if you like */
printf("Enter a number: ");
if (fgets(line, sizeof(line), stdin) == 0)
{
fprintf(stderr, "Unexpected EOF\n");
exit(EXIT_FAILURE);
}
errno = 0;
char *eon; /* End of number */
long result = strtol(line, &eon, 0);
if (eon == line)
{
fprintf(stderr, "What you entered is not a number: %s\n", line);
exit(EXIT_FAILURE);
}
if (sizeof(int) == sizeof(long))
{
if ((result == LONG_MIN || result == LONG_MAX) && errno == ERANGE)
{
fprintf(stderr, "What you entered is not a number in the range %ld..%+ld: %s\n",
LONG_MIN, LONG_MAX, line);
exit(EXIT_FAILURE);
}
}
else
{
if ((result == LONG_MIN || result == LONG_MAX) && errno == ERANGE)
{
fprintf(stderr, "What you entered is not a number in the range %ld..%+ld,\n"
"let alone in the range %d..%+d: %s\n",
LONG_MIN, LONG_MAX, INT_MIN, INT_MAX, line);
exit(EXIT_FAILURE);
}
}
char c;
while ((c = *eon++) != '\0')
{
if (!isspace((unsigned char)c))
{
fprintf(stderr, "There is trailing information (%c) after the number: %s\n",
c, line);
exit(EXIT_FAILURE);
}
}
if (result < INT_MIN || result > INT_MAX)
{
fprintf(stderr, "What you entered is outside the range %d..%+d: %s\n",
INT_MIN, INT_MAX, line);
exit(EXIT_FAILURE);
}
int i = result; /* No truncation given prior tests */
printf("%d is a valid int\n", i);
return(EXIT_SUCCESS);
}
That seems to work correctly for a fairly large collection of weird numeric and non-numeric inputs. The code handles both 32-bit systems where sizeof(long) == sizeof(int)
and LP64 64-bit systems where sizeof(long) > sizeof(int)
— you probably don't need that. You can legitimately decide not to detect all the separate conditions but to aggregate some of the errors into a smaller number of error messages.
C: scanf input single character and validation
#include <stdio.h>
char GetStuff(void) {
char c;
scanf("%c", &c);
getchar();
while ((c != 'A') && (c != 'a') && (c != 'P') && (c != 'p')) {
printf("invalid input, enter again (A for AM or P for PM): ");
scanf ("%c", &c);
getchar();
}
return c;
}
int main(void) {
printf("Calling GetStuff()...\n");
char x = GetStuff();
printf("User entered %c\n", x);
return 0;
}
You are using while (c != 'A' || c != 'P')
as your loop conditional, but this will always return true. What you meant to use is the && "and" operator, instead of the || "or" operator.
Also, call getchar()
after your scanf statements, to capture the newline. This should work the way you want it to.
Validate input in C
You have two problems in the code as shown in the question (before the edit):
You call
scanf
twice for the same variables, forcing the user to input the same data twice.The format used will not read a newline, so the expression
check != '\n'
will always be true.
For number 1, just remove the first scanf
call. For number 2, the user must press the Enter key anyway to end the input, so no need to check for that. If you really want to be sure, then use e.g. fgets
to read a line with both number in it, and use sscanf
to parse the values.
How validate user input when the expected value is of type int and the entered value is not of type int?
Personally, I advise ditching scanf
altogether for interactive user input, especially for numeric input. It just isn't robust enough to handle certain bad cases.
The %d
conversion specifier tells scanf
to read up to the next non-numeric character (ignoring any leading whitespace). Assume the call
scanf("%d", &val);
If your input stream looks like {'\n', '\t', ' ', '1', '2', '3', '\n'}, scanf
will skip over the leading whitespace characters, read and convert "123", and stop at the trailing newline character. The value 123
will be assigned to val
, and scanf
will return a value of 1, indicating the number of successful assignments.
If your input stream looks like {'a', 'b', 'c', '\n'}, scanf
will stop reading at the a
, not assign anything to val
, and return 0 (indicating no successful assignments).
So far, so good, right? Well, here's an ugly case: suppose your user types in "12w4". You'd probably like to reject this entire input as invalid. Unfortunately, scanf
will happily convert and assign the "12" and leave the "w4" in the input stream, fouling up the next read. It will return a 1, indicating a successful assignment.
Here's another ugly case: suppose your user types in an obnoxiously long number, like "1234567890123456789012345678901234567890". Again, you'd probably like to reject this input outright, but scanf
will go ahead and convert and assign it, regardless of whether the target data type can represent that value or not.
To properly handle those cases, you need to use a different tool. A better option is to read the input as text using fgets
(protecting against buffer overflows), and manually convert the string using strtol
. Advantages: you can detect and reject bad strings like "12w4", you can reject inputs that are obviously too long and out of range, and you don't leave any garbage in the input stream. Disadvantages: it's a bit more work.
Here's an example:
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
...
#define DIGITS ... // maximum number of digits for your target data type;
// for example, a signed 16-bit integer has up to 5 digits.
#define BUFSIZ (DIGITS)+3 // Account for sign character, newline, and 0 terminator
...
char input[BUFSIZ];
if (!fgets(input, sizeof input, stdin))
{
// read error on input - panic
exit(-1);
}
else
{
/**
* Make sure the user didn't enter a string longer than the buffer
* was sized to hold by looking for a newline character. If a newline
* isn't present, we reject the input and read from the stream until
* we see a newline or get an error.
*/
if (!strchr(input, '\n'))
{
// input too long
while (fgets(input, sizeof input, stdin) && !strchr(input, '\n'))
;
}
else
{
char *chk;
int tmp = (int) strtol(input, &chk, 10);
/**
* chk points to the first character not converted. If
* it's whitespace or 0, then the input string was a valid
* integer
*/
if (isspace(*chk) || *chk == 0)
val = tmp;
else
printf("%s is not a valid integer input\n", input);
}
}
proper use of scanf in a while loop to validate input
I took @4386427 idea and just added codes to cover what it missed (leading spaces and + sign), I tested it many times and it is working perfectly in all possible cases.
#include<stdio.h>
#include <ctype.h>
#include <stdlib.h>
int validate_line (char *line);
int main(){
char line[256];
int y=0;
long x;
while (y<5){
printf("Please Insert X Value\n");
if (fgets(line, sizeof(line), stdin)){//return 0 if not execute
if (validate_line(line)>0){ // check if the string contains only numbers
x =strtol(line, NULL, 10); // change the authentic string to long and assign it
printf("This is x %d" , x);
break;
}
else if (validate_line(line)==-1){printf("You Have Not Inserted Any Number!.... ");}
else {printf("Invalid Input, Insert Integers Only.... ");}
}
y++;
if (y==5){printf("NO MORE RETRIES\n\n");}
else{printf("%d Retries Left\n\n", (5-y));}
}
return 0;}
int validate_line (char *line){
int returned_value =-1;
/*first remove spaces from the entire string*/
char *p_new = line;
char *p_old = line;
while (*p_old != '\0'){// loop as long as has not reached the end of string
*p_new = *p_old; // assign the current value the *line is pointing at to p
if (*p_new != ' '){p_new++;} // check if it is not a space , if so , increment p
p_old++;// increment p_old in every loop
}
*p_new = '\0'; // add terminator
if (*line== '+' || *line== '-'){line++;} // check if the first char is (-) or (+) sign to point to next place
while (*line != '\n'){
if (!(isdigit(*line))) {return 0;} // Illegal char found , will return 0 and stop because isdigit() returns 0 if the it finds non-digit
else if (isdigit(*line)){line++; returned_value=2;}//check next place and increment returned_value for the final result and judgment next.
}
return returned_value; // it will return -1 if there is no input at all because while loop has not executed, will return >0 if successful, 0 if invalid input
}
Validating input using scanf (check whether input is char or int)
scanf()
is expecting a number if it finds invalid input(something that doesn't match the format provided) it will return leaving the input buffer as is, that means leaving this invalid input there, then the loop condition evaluates to true and scanf()
tries to read a number again to find the same invalid input as before, this repeats forever.
cleaning the input buffer will fix this, you can clean it like this:
#include <stdio.h>
void cleanBuffer(){
int n;
while((n = getchar()) != EOF && n != '\n' );
}
int main(int argc, char *argv[])
{
int num = -1;
do
{
printf("num :");
scanf("%d", &num);
cleanBuffer(); //we clean the buffer here
}
while(num<0 || num>=100);
return 0;
}
Related Topics
How Do Compilers Treat Variable Length Arrays
How to Find Where an Exception Was Thrown in C++
Where Do I Find the Definition of Size_T
Simplest Way to Determine Return Type of Function
Most Efficient/Elegant Way to Clip a Number
Why Are Some Functions in <Cmath> Not in the Std Namespace
Is the Size of a Struct Required to Be an Exact Multiple of the Alignment of That Struct
How to Pass a Member Function to a Function Pointer
Sorting a Std::Vector<Std::Pair<Std::String,Bool>> by the String
Behavior When Dereferencing the .End() of a Vector of Strings
What Does String::Npos Mean in This Code
Is Using #Pragma Warning Push/Pop the Right Way to Temporarily Alter Warning Level
How to Make Visual Studio Use the Native Amd64 Toolchain
Std::To_String - More Than Instance of Overloaded Function Matches the Argument List