Scanf and loops - c

here a a piece of code that is supposed to loop over and over until the user inputs a number between 0 and 15
int input;
do
{
printf("Enter a number => ");
scanf("%d",&input);
printf("\n %d \n",input);
}
while (!(input>0 && input <15));
However if the user puts in something like "gfggdf" it results in the loop repeating over and over and over and never prompting the user for input again... the console looks like this
Enter a number =>
0
Enter a number =>
0
Enter a number =>
0
Enter a number =>
0
Enter a number =>
0
Enter a number =>
0
(looping indefinitely)
After looking through this book, it seems I need to do something like this to prevent this from happening.
int input, error, status;
char skip_ch;
do
{
error = 0;
printf("Enter a number => ");
status = scanf("%d",&input);
printf("\n %d \n",input);
if(status!=1)
{
error=1;
}
else if(input < 0 || input >15){
error = 1;
}
do
{
scanf("%c",&skip_ch);
}while(skip_ch != '\n');
}
while (error);
I understand needing to check for the scanf status to make sure it was valid input, What I don't understand the the inner do-while loop. I feel like the book never really explained to me why scanf needs to be called several times like that, it's like somehow the scanf buffer got filled up with a bunch of garbage and it's somehow cleaning it out by just looping through it a million times for you.
Could anyone please explain this black magic to me?

That's why there are so many answers to similar questions that recommend not using scanf().
Also, you should check the return value from scanf() because it tells you when something has gone wrong - and no value could be converted.
The normal recommended solution is a combination of fgets() to read lines of input and sscanf() to parse the line once it is read.
The second loop in your example goes around reading the characters up to and including a newline, thus resynchronizing scanf() and skipping any bogus data. It is a long-winded way of doing it; it would be simpler to use getchar() instead:
int c;
while ((c = getchar()) != EOF && c != '\n')
;
Note the use of 'int c;' - the type is crucially not 'char' or 'unsigned char' because it must store all possible char values plus EOF.

The original code loops indefinitely because the invalid data (the "gfggdf") is not removed from the input buffer when scanf fails to convert it to an integer -- it's left in the input buffer, so the next call to scanf looks at the same data, and (of course) still can't convert it to an integer, so the loop executes yet again, and the results still haven't changed.
The:
do {
scanf("%c",&skip_ch);
}while(skip_ch != '\n');
simply reads one character at a time (and ignores it) until it gets to the end of the line. If you prefer to get rid of the garbage in the input buffer without a loop, you can use something like: scanf("%*[^\n]");
Another possibility that's (probably more) widely used is to start by reading an entire line, then attempting to convert what's in the line. Whether it converts or not, you've already read all the data to the end of the line so the next attempt at reading/converting will start from the next line whether the conversion worked or not. This does have one potential weakness: if you got a really long line of input, you could end up reading and storing a lot of data for which you have no real use. It's rarely a big deal, but you should still be aware of it.

First of all, avoid using scanf(), use fgets() instead.
To evaluate your condition (first code). 0 > 0 is false. 0 < 15 is true. false && true is false. !false is true. That's why it's looping indefinitely.

Related

Scanf not working in while loop when nonmatching string is entered

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.

Terminate scanf on EOF and ignore strings

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

scanf for string and number in C

I've been trying to use scanf() for reading string and integer. It works fine, but I am unable to check if the input is given correctly.
char command[6];
int cmd_num;
scanf("%5s %d", command, &cmd_num);
It works fine for reading the right input, I am able to check if the string is right by strcmp. I tried to check the number by the function isdigit(), but it cannot check correctly, I guess because of whitespaces, but I am not sure exactly how it works.
I tried to google this, I played around with [^\n] but still it doesn't work. Could anyone enlighten me how scanf exactly works please?
I think solution would be if I exactly could tell scanf what to read ->string(space)integer. Is it possible to acquire this with regular expressions or any other way?
What I need basically is to scanf read the line recognize the string and then to check if there is a number after it too.
Another question is, I need to read the input for as long as the user is giving it, I tried to use getchar for while loop, to repeat as long as the char is not '\n' but this closes the loop right in the beginning, but if I give it EOF condition it never ends even with multiple newlines. I guess it could be limited by the scanf too, but I am not sure exactly how, threads on this forum couldn't give me the answer for it.
This will not procced:
while ( ( c = getchar() ) != '\n')
{
//code
}
This will proceed no matter of newlines:
while ( (c = getchar() ) != EOF )
{
//code
}
You don't check if a number is a number, and isdigit() is used to check if an ascii value is a number between 0 and 9.
To check that scanf() succeeded and the input is correct, thus the value of cmd_num is correct and is an integer read from the input, you have to check the return value of scanf().
It returns, the number of matched format specifiers. You have two of them so
if (scanf("%5s%d", command, &cmd_num) == 2) // this means it's correct
// ^ your code is missing the address of operator

Do while loop not exiting despite expression becoming false

I've got a program here which contains a do-while loop within a specified void method. I'm trying to exit the loop within the function, so that the do-while loop actually works as it is supposed to. Except after I run the program and one of the cases occurs, the program continues to run despite my while statement stating that it should only work while(userInput != 1).
I cannot use global variables to solve this problem, as my assignment limits me on using such techniques, thus any help would be much appreciated!
Here is a snippet of my code:
void functionTest()
{
int gameOver = 0;
int userInput;
do
{
printf("please enter a number 1-3");
scanf("%d",&userInput);
switch(userInput)
{
case 1:
printf("You entered %d",userInput);
gameOver = 1;
break;
case 2:
printf("You entered %d",userInput);
gameOver = 1;
break;
case 3:
printf("You entered %d",userInput);
gameOver = 1;
break;
}
}
while(gameOver!= 1);
}
}
The problem probably lies when you use scanf(). Something that you're inputting before hitting enter is not 1, 2 or 3. Could you tell us exactly what you type when it asks you to enter a choice?
Sometimes, the standard output needs to be flushed before using a fresh scanf(). Try fflush(stdout) before the scanf line.
See older question 1 and older question 2.
EDIT:
I can reproduce the problem easily enough if I enter anything apart from "1","2" or "3"...
I would suggest, you do the following before executing the switch statement:
Add fflush(stdout) before scanf()
Accept the input as a string (%s) instead of a number. (char [] needed)
Trim the string of trailing and leading white spaces.
Convert to number using a library function
Then switch-case based on that number
The problem is that if other characters (that aren't part of an integer) are present in the input stream before an integer can be read, scanf() fails and unusable data is never cleared out... which leads to an infinite loop (where scanf() repeatedly fails to read the same characters as an integer, over and over).
So you need to read off the invalid characters when scanf() fails, or as part of the format.
A simple fix would be to change your scanf from:
scanf("%d",&userInput);
to:
scanf("%*[^0-9]%d",&userInput);
to read (and discard) any characters in the input stream that aren't digits 0-9 before reading your integer... but that still doesn't check whether scanf fails for any other reason (like a closed input stream).
You could replace it with something like this:
int scanfRes,c;
do {
scanfRes = scanf("%d",&userInput); /* try to read userInput */
/* ..then discard remainder of line */
do {
if ((c = fgetc(stdin)) == EOF)
return; /* ..return on error or EOF */
} while (c != '\n');
} while (scanfRes != 1); /* ..retry until userInput is assigned */
..which will retry scanf() until the field is assigned, discarding the remainder of the line after each attempt, and exiting the function if fgetc() encounters an error or EOF when doing so.

scanf ignoring, infinite loop

int flag = 0;
int price = 0;
while (flag==0)
{
printf("\nEnter Product price: ");
scanf("%d",&price);
if (price==0)
printf("input not valid\n");
else
flag=1;
}
When I enter a valid number, the loop ends as expected. But if I enter something that isn't a number, like hello, then the code goes into an infinite loop. It just keeps printing Enter Product price: and input not valid. But it doesn't wait for me to enter a new number. Why is that?
When you enter something that isn't a number, scanf will fail and will leave those characters on the input. So if you enter hello, scanf will see the h, reject it as not valid for a decimal number, and leave it on the input. The next time through the loop, scanf will see the h again, so it just keeps looping forever.
One solution to this problem is to read an entire line of input with fgets and then parse the line with sscanf. That way, if the sscanf fails, nothing is left on the input. The user will have to enter a new line for fgets to read.
Something along these lines:
char buffer[STRING_SIZE];
...
while(...) {
...
fgets(buffer, STRING_SIZE, stdin);
if ( sscanf(buffer, "%d", &price) == 1 )
break; // sscanf succeeded, end the loop
...
}
If you just do a getchar as suggested in another answer, then you might miss the \n character in case the user types something after the number (e.g. a whitespace, possibly followed by other characters).
You should always test the return value of sscanf. It returns the number of conversions assigned, so if the return value isn't the same as the number of conversions requested, it means that the parsing has failed. In this example, there is 1 conversion requested, so sscanf returns 1 when it's successful.
The %d format is for decimals. When scanf fails (something other a decimal is entered) the character that caused it to fail will remain as the input.
Example.
int va;
scanf("%d",&va);
printf("Val %d 1 \n", val);
scanf("%d",&va);
printf("Val %d 2 \n", val);
return 0;
So no conversion occurs.
The scanf function returns the value of the macro EOF if an input failure occurs before
any conversion. Otherwise, the scanf function returns the number of input items
assigned, which can be fewer than provided for, or even zero, in the event of an early
matching failure
7.19.6. The scanf function - JTC1/SC22/WG14 - C
So you should note that scanf returns its own form of notice for success
int scanf(char *format)
so you could have also did the following
do {
printf("Enter Product \n");
}
while (scanf("%d", &sale.m_price) == 1);
if(scanf("%d", &sale.m_price) == 0)
PrintWrongInput();
Also keep in the back of your head to try to stay away from scanf. scanf or scan formatted should not be used for interactive user input. See the C FAQ 12.20
After the first number, a '\n' will be in the input buffer (the return you pressed to input the number), so in the second iteration the scanf call will fail (becouse \n isn't a number), scanf will not remove that \n from the buffer, so in the next iteration it will fail again and so on.
You can fix that by reading the '\n' with a getchar() call after scanf.
The "answers" that say it will because there is a '\n' in the buffer are mistaken -- scanf("%d", ...) skips white space, including newlines.
It goes into an infinite loop if x contains 0 and scanf encounters a non-number (not just whitespace) or EOF because x will stay 0 and there's no way for it to become otherwise. This should be clear from just looking at your code and thinking about what it will do in that case.
It goes into an infinite loop because scanf() will not consumed the input token if match fails. scanf() will try to match the same input again and again. you need to flush the stdin.
if (!scanf("%d", &sale.m_price))
fflush(stdin);
Edit: Back when I first wrote this answer, I was so stupid and ignorant about how scanf() worked.
First of all let me clear something, scanf() is not a broken function, if I don't know how scanf() works and I don't know how to use it, then I probably haven't read the manual for scans() and that cannot be scanf()'s fault.
Second in order to understand what is wrong with your code you need to know how scanf() works.
When you use scanf("%d", &price) in your code, the scanf() tries to read in an integer from the input, but if you enter a non numeric value, scanf() knows it isn't the right data type, so it puts the read input back into the buffer, on the next loop cycle however the invalid input is still in the buffer which will cause scanf() to fail again because the buffer hasn't been emptied, and this cycle goes on forever.
In order to tackle this problem you can use the return value of scanf(), which will be the number of successful inputs read, however you need to discard the invalid inputs by flushing the buffer in order to avoid an infinite loop, the input buffer is flushed when the enter key is pressed, you can do this using the getchar() function to make a pause to get an input, which will require you to press the enter key thus discarding the invalid input, note that, this will not make you press the enter key twice whether or not you entered the correct data type, because the newline character will still be in the buffer. After scanf() has successfully finished reading the integer from input, it will put \n back into the buffer, so getchar() will read it, but since you don't need it, it's safe to discard it:
#include <stdio.h>
int main(void)
{
int flag = 0;
int price = 0;
int status = 0;
while (flag == 0 && status != 1)
{
printf("\nEnter Product price: ");
status = scanf("%d", &price);
getchar();
if (price == 0)
printf("input not valid\n");
else
flag = 1;
}
return 0;
}

Resources