How to Validate Input Using Scanf

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 POSIX
getline() to read the line, and
strtol() 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 an unsigned char or shall equal the value of the macro EOF. 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):

  1. You call scanf twice for the same variables, forcing the user to input the same data twice.

  2. 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



Leave a reply



Submit