I'm using a function called checkType to check whether the user has entered a valid input of integer type. For example, if the user enters 15 it will print valid and 15c will print not valid. However, if the user enters a string input only like ccccc, it results in an infinite loop and the program crashes. I have added some screenshots below to show the outputs.
int checkType(int input, char *c) {
if (input == 2 and *c == '\n') {
return 1;
} else {
return 0;
};
}
int main(void) {
int faces = 0;
char c;
int valid = 0;
int input;
while (valid == 0) {
printf("Enter number of faces: ");
input = scanf("%d%c", &faces, &c);
valid = checkType(input, &c);
printf(valid == 0 ? "not valid\n" : "valid\n");
}
}
Infinite Loop:
The scanf() family of functions is not really made for input of questionable syntax.
The usual approach to solve your problem is to read in all the input in a way which is sure to succeed, e.g. by reading a complete line (instead of a number, word-like string, or anything else with expected format) with fgets().
Then you can try to parse that line-representing string as by expected format. (You can use sscanf() for the parsing attempt.) If that fails you ignore it and read the next line, or try parsing according to an alternative allowed format for the same input.
The relevant difference is that reading a whole line will succeed and remove it from the input stream. In contrast to that, your code, in case of syntax failure, leaves in the input stream whatever did not match the expected syntax. As such it will, of course, fail the next iteration of the reading loop again. That is what causes your endless loop.
In detail:
read a whole line into a buffer,
using fgets() and the option to restrict the number of characters to the size of the buffer
at this point all of the line is removed from the input (aka stdin, aka input stream),
which means that the next read will get new input even if the read line does not match any allowed format,
this is the relevant difference to trying to read input directly with scanf()
from the line buffer, attempt to read separate values according to an allowed format,
using sscanf() and with a check of the return value
if successful great, you have your expected variables filled (e.g. an integer);
you could try scanning for additional, not format-covered trailing input (and continue like for incorrect input, even if the beginning of the line matched an allowed format)
if not successful try a different allowed format,
using sscanf() again,
from the same line buffer, which is not changed, not even if a format partially matched
if no allowed format matches the input it is time to consider it incorrect
incorrect input can be ignored or can cause a fatal parsing error in your program, your choice
However, if the user enters a string input only like ccccc,
it results in an infinite loop and the program crashes
Reason : scanf returns the number of valid read values from stdin. If we give cccc as input, scanf cannot read the value, because the input and variable faces are of different data type. This makes input = 0. So the function checkType returns 0, which in turn makes valid = 0. This makes while(valid == 0) always true and hence the endless loop.
Scanf will read form stdin, and not clean the stdin buffer if not match. So it will read wrong data again in your case, you can just add a clean function with stdin after scanf, like: __fpurge(stdin)
You can refer to the following two links:
https://man7.org/linux/man-pages/man3/scanf.3.html
http://c-faq.com/stdio/stdinflush2.html
With input like "ccccc" and scanf("%d%c" ..., there is no conversion to an int. scanf() returns 0 and stdin remains unchanged. Calling the function again has the same result.
Code neds to consume errant input - somehow.
Research fgets() to read a line of user input.
Related
I am using the following code to scan for integers input from the user until end of line occurs.
while (scanf(" %d", &num) != EOF) {
printf("Do something")
}
This works as expected until the user inputs a string instead of an integer. The program would then endlessly keep printing Do something. Why is that happening?
How can I stop the loop only when End of line occurs, but ignore string inputs and only perform my logic if integer inputs have occured?
scanf() returns the number of input items successfully assigned. That is, in your example, 1 if a number is entered, or 0 otherwise. (Unless an input error occurs prior to the first input item, in which case it returns EOF.)
In case a string is entered, this fails to match %d, scanf() returns zero, the loop is entered, "Do something" is printed, and scanf() is called again.
But the string has not been consumed by any input function.
So the string fails to match, "Do something" is printed... you get the idea.
Be happy you do not access num, because if you haven't initialized that beforehand, accessing it would be undefined behaviour (as it still isn't initialized)...
Generally speaking, do not use scanf() on potentially malformed (user) input. By preference, read whole lines of user input with fgets() and then parse them in-memory with e.g. strtol(), strtof(), strtok() or whatever is appropriate -- this allows you to backtrack, identify exactly the point where the input failed to meet your expectations, and print meaningful error messages including the full input.
How can I stop the loop only when End of line occurs, but ignore string inputs and only perform my logic if integer inputs have occured?
When scanf(" %d", &num) returns 0, read a single character and toss it.
int count;
while ((count = scanf("%d", &num)) != EOF) {
if (count > 0) printf("Do something with %d\n", num);
else getchar();
}
I came across this problem when I want to check what I input is number. The scanf function will return 1 if I successfully input a number. So here is what I wrote:
int argu;
while(scanf("%d",&argu)!=1){
printf("Please input a number!\n");
}
But when I input things like abcd to it, the loop would go forever and not stop for prompt.
I looked it up online and found that it had something to do with the cache and I need to clean it up so scanf can get new data. So I tried fflush but it didn't work.
Then I saw this:
int argu,j;
while(scanf("%d",&argu)!=1){
printf("Please input a number!\n");
while((j=getchar())!='\n' && j != '\n');
}
Then when I input things like 'abcd' it worked well and it prompted for my input. But when I input things like '12ab', it wouldn't work again.
So is there a way I can check the input for scanf("%d", &argu) is actually a number and prompt for another input if it isn't?
EDIT:
I saw the answers and solved my problem by using while(*eptr != '\n').
Notice that the fgets function actually reads '\n' into the array and gets doesn't. So be careful.
It's better to read a full line, using fgets(), and then inspecting it, rather than trying to parse "on the fly" from the input stream.
It's easier to ignore non-valid input, that way.
Use fgets() and then just strtol() to convert to a number, it will make it easy to see if there is trailing data after the number.
For instance:
char line[128];
while(fgets(line, sizeof line, stdin) != NULL)
{
char *eptr = NULL;
long v = strtol(line, &eptr, 10);
if(eptr == NULL || !isspace(*eptr))
{
printf("Invalid input: %s", line);
continue;
}
/* Put desired processing code here. */
}
But when I input things like abcd to it, the loop would go forever and not stop for prompt.
That's because if scanf encounters a character that does not match the conversion specifier, it leaves it in the input stream. Basically, what's happening is that scanf reads the character a from the input stream, determines that it's not a valid match for the %d conversion specifier, and then pushes it back onto the input stream. The next time through the loop it does the same thing. And again. And again. And again.
fflush is not a good solution, because it isn't defined to work on input streams.
For the input "12ab", scanf will read and convert "12", leaving "ab" in the input stream.
The best solution is to read all your input as text, then convert to numeric types using strtol (for integral values) and strtod (for real values). For example:
char input[SIZE]; // assume SIZE is big enough for whatever input we get
int value;
if (fgets(input, sizeof input, stdin) != NULL)
{
char *chk;
int tmp = (int) strtol(input, &chk, 10);
if (isspace(*chk) || *chk == 0)
value = tmp;
else
printf("%s is not a valid integer string\n", input);
}
chk points to the first character in the input stream that isn't a decimal digit. If this character is not whitespace or the 0 terminator, then the input string wasn't a valid integer. This will detect and reject inputs like "12ab" as well as "abcd".
scanf is a good solution if you know your input is always going to be properly formed and well-behaved. If there's a chance that your input isn't well-behaved, use fgets and convert as needed.
I will suggest to get input as a string and check for non-numeric characters in it. If input is valid convert string to int by sscanf(str,"%d",&i); or else diplay error.
Just call scanf("%*[^\n]\n") inside the loop, and it will discard the "cache".
Call scanf("%*[^\n]\n") inside the loop. This should be enough to discard anything associated with the cache.
The input and output for the following program are given below:
#include<stdio.h>
int main(){
int a=0, b=100, c=200;
scanf("%d,%d,%d",&a,&b,&c);
printf("%d %d %d\n",a,b,c);
return 0;
}
Input-1:
1,2,3
Output-1:
1 2 3
So the first output is correct and as expected.
Input-2:
1 2 3
Output-2:
1 100 200
Here it reads first integer correctly but I am not able to understand, how the scanf() reads the data after first integer when we are not giving the input in specified format?
As values entered after first integer are not assigned to any variables, What happens to those values?
Are they written on some random memory locations?
Here it reads first integer correctly but I am not able to understand,
how the scanf() reads the data after first integer when we are not
giving the input in specified format?
scanf stops at the first mismatch and leaves the rest of the target objects untouched. You can inspect the return value to determine how many "items" scanf matched.
As values entered after first integer are not assigned to any
variables, What happens to those values?
The unmatched data is left in the input buffer, available for a subsequent read using scanf, fgets etc.
I used getchar() and putchar() after last printf() statement in the
program but nothing was read.
Strange. You should be able to get away with something like:
int ch;
while ((ch = getc(stdin)) != EOF)
putchar(ch);
TL;DR answer: It doesn't. It stops reading.
To clarify, input is not in specified format is a matching failure for scanf(). That is why it's always recommended to check the return value of scanf() to ensure all the input items got scanned successfully.
In the second input scenario, the scanf() has failed to scan all the input parameters because of the format mismatch of expected and received inputs [and you have no idea of that]. Only the value of a has been scanned successfully, and reflected.
[Just for sake of completeness of the answer]:
After the input value of a, due to the absence of a , in the input, mismatch happened and scanf() stopped scanning, returning a value of 1. That's why, b and c prints out their initial values.
I was trying to scan only allowed types in my c program.
I have a variable which is integer.I want to scan again while user input is character.
int usercount;
do{
printf("enter user count");
scanf("%d",&usercount);
}
while(!isdigit(usercount));
But when input is char it's in a infinite loop.Can anyone help?
The scanf() function has a return value. It tells you how many "conversions" it succeeded in doing. In this case, it will return 0 if failed to interpret the input as an integer. Your code should inspect the return value.
In general, to be less sensitive with "stuff" left in the buffered input queue, you should read a full line at a time with fgets(), and then use sscanf() (or any other parsing function, like strtoul()) to parse the data.
I came across this problem when I want to check what I input is number. The scanf function will return 1 if I successfully input a number. So here is what I wrote:
int argu;
while(scanf("%d",&argu)!=1){
printf("Please input a number!\n");
}
But when I input things like abcd to it, the loop would go forever and not stop for prompt.
I looked it up online and found that it had something to do with the cache and I need to clean it up so scanf can get new data. So I tried fflush but it didn't work.
Then I saw this:
int argu,j;
while(scanf("%d",&argu)!=1){
printf("Please input a number!\n");
while((j=getchar())!='\n' && j != '\n');
}
Then when I input things like 'abcd' it worked well and it prompted for my input. But when I input things like '12ab', it wouldn't work again.
So is there a way I can check the input for scanf("%d", &argu) is actually a number and prompt for another input if it isn't?
EDIT:
I saw the answers and solved my problem by using while(*eptr != '\n').
Notice that the fgets function actually reads '\n' into the array and gets doesn't. So be careful.
It's better to read a full line, using fgets(), and then inspecting it, rather than trying to parse "on the fly" from the input stream.
It's easier to ignore non-valid input, that way.
Use fgets() and then just strtol() to convert to a number, it will make it easy to see if there is trailing data after the number.
For instance:
char line[128];
while(fgets(line, sizeof line, stdin) != NULL)
{
char *eptr = NULL;
long v = strtol(line, &eptr, 10);
if(eptr == NULL || !isspace(*eptr))
{
printf("Invalid input: %s", line);
continue;
}
/* Put desired processing code here. */
}
But when I input things like abcd to it, the loop would go forever and not stop for prompt.
That's because if scanf encounters a character that does not match the conversion specifier, it leaves it in the input stream. Basically, what's happening is that scanf reads the character a from the input stream, determines that it's not a valid match for the %d conversion specifier, and then pushes it back onto the input stream. The next time through the loop it does the same thing. And again. And again. And again.
fflush is not a good solution, because it isn't defined to work on input streams.
For the input "12ab", scanf will read and convert "12", leaving "ab" in the input stream.
The best solution is to read all your input as text, then convert to numeric types using strtol (for integral values) and strtod (for real values). For example:
char input[SIZE]; // assume SIZE is big enough for whatever input we get
int value;
if (fgets(input, sizeof input, stdin) != NULL)
{
char *chk;
int tmp = (int) strtol(input, &chk, 10);
if (isspace(*chk) || *chk == 0)
value = tmp;
else
printf("%s is not a valid integer string\n", input);
}
chk points to the first character in the input stream that isn't a decimal digit. If this character is not whitespace or the 0 terminator, then the input string wasn't a valid integer. This will detect and reject inputs like "12ab" as well as "abcd".
scanf is a good solution if you know your input is always going to be properly formed and well-behaved. If there's a chance that your input isn't well-behaved, use fgets and convert as needed.
I will suggest to get input as a string and check for non-numeric characters in it. If input is valid convert string to int by sscanf(str,"%d",&i); or else diplay error.
Just call scanf("%*[^\n]\n") inside the loop, and it will discard the "cache".
Call scanf("%*[^\n]\n") inside the loop. This should be enough to discard anything associated with the cache.