Why is scanf() causing infinite loop in this code?
Solution 1
scanf
consumes only the input that matches the format string, returning the number of characters consumed. Any character that doesn't match the format string causes it to stop scanning and leaves the invalid character still in the buffer. As others said, you still need to flush the invalid character out of the buffer before you proceed. This is a pretty dirty fix, but it will remove the offending characters from the output.
char c = '0';
if (scanf("%d", &number) == 0) {
printf("Err. . .\n");
do {
c = getchar();
}
while (!isdigit(c));
ungetc(c, stdin);
//consume non-numeric chars from buffer
}
edit: fixed the code to remove all non-numeric chars in one go. Won't print out multiple "Errs" for each non-numeric char anymore.
Here is a pretty good overview of scanf.
Solution 2
scanf()
leaves the "a
" still in the input buffer for next time. You should probably use getline()
to read a line no matter what and then parse it with strtol()
or similar instead.
(Yes, getline()
is GNU-specific, not POSIX. So what? The question is tagged "gcc" and "linux". getline()
is also the only sensible option to read a line of text unless you want to do it all by hand.)
Solution 3
I think you just have to flush the buffer before you continue with the loop. Something like that would probably do the job, although I can't test what I am writing from here:
int c;
while((c = getchar()) != '\n' && c != EOF);
Solution 4
Rather than using scanf()
and have to deal with the buffer having invalid character, use fgets()
and sscanf()
.
/* ... */
printf("0 to quit -> ");
fflush(stdout);
while (fgets(buf, sizeof buf, stdin)) {
if (sscanf(buf, "%d", &number) != 1) {
fprintf(stderr, "Err...\n");
} else {
work(number);
}
printf("0 to quit -> ");
fflush(stdout);
}
/* ... */
Solution 5
Due to the problems with scanf
pointed out by the other answers, you should really consider using another approach. I've always found scanf
way too limited for any serious input reading and processing. It's a better idea to just read whole lines in with fgets
and then working on them with functions like strtok
and strtol
(which BTW will correctly parse integers and tell you exactly where the invalid characters begin).
Admin
Updated on July 17, 2022Comments
-
Admin almost 2 years
I've a small C-program which just reads numbers from stdin, one at each loop cycle. If the user inputs some NaN, an error should be printed to the console and the input prompt should return again. On input of "0", the loop should end and the number of given positive/negative values should be printed to the console. Here's the program:
#include <stdio.h> int main() { int number, p = 0, n = 0; while (1) { printf("-> "); if (scanf("%d", &number) == 0) { printf("Err...\n"); continue; } if (number > 0) p++; else if (number < 0) n++; else break; /* 0 given */ } printf("Read %d positive and %d negative numbers\n", p, n); return 0; }
My problem is, that on entering some non-number (like "a"), this results in an infinite loop writing "-> Err..." over and over. I guess it's a scanf() issue and I know this function could be replace by a safer one, but this example is for beginners, knowing just about printf/scanf, if-else and loops.
I've already read the answers to the question
scanf()
skips every otherwhile
loop in C and skimmed through other questions, but nothing really answer this specific problem. -
pmg over 14 years"premature optimization is the root of all evil" ... but switch the constants:
'\n'
is much more likely to show up thanEOF
:) -
Teddy over 14 yearsIf the input is "abc", that code will print "Err. . ." three times.
-
Tim Post over 14 yearsYou can not rely on non standard extensions for something as crucial as user input without providing them in your own tree in case they do not exist. If you edit your answer to reflect this, I will withdraw my down vote.
-
Andomar over 14 years@tinkertim: the question specifies gcc on Linux, guaranteeing that
strtol
is available -
Tim Post over 14 yearsAlso, at least hinting how to turn such extension ON may help :)
-
Tim Post over 14 years@Andomar: It was getline() that I was taking issue with ;)
-
Andomar over 14 yearsYou'd hope
EOF
is 100% guaranteed to show up; otherwise, you either have a really fast keyboard or a really slow CPU -
Admin over 14 yearsAdding this while-loop before the if-statement does result in a wrong program behaviour. To be exact, the "->" prompt isn't shown after the first input, may it either be right or wrong.
-
pmg over 14 yearsYour
while
loop will consume everything,'\n'
included. -
Admin over 14 yearsAfaik fflush() doesn't work the same way on each system. At least on my Linux box, the fflush(stdout) doesn't help to show up the "->" prompt. Also, a call to setvbuf() doesn't help here, too.
-
Jamison Dance over 14 yearsYeah, it is pretty ghetto. I will tweak it a bit.
-
Roman Nikitchenko over 14 yearsfgets() read some buffer and if it doesn't contain format right from the beginning the whole line is thrown away. This could be not acceptable (but could be desired, it depends on requirements).
-
caf over 14 yearsNow if the input is "ab-10", it will incorrectly remove the minus sign from the input and read "10" as the next number.
-
Admin over 10 yearsI see that you are using goto, don't!
-
alexia over 9 years@TimPost Both getline() and getdelim() were originally GNU extensions. They were standardized in POSIX.1-2008.
-
Jonathan Leffler over 9 yearsPlease read Using
fflush(stdin)
— especially the comments to the question — for information about this. It works on Windows because Microsoft documents that it does; it doesn't work anywhere else (that I know of) in practice, notwithstanding some documentation suggesting the contrary. -
Michael Armbruster almost 9 yearsI know it's old, but just change it to
while (!isdigit(c) && c != '-');
, that should also help with minus signs. -
ilgaar over 8 yearsThis still doesn't solve the problem entirely, since if you enter a combination of alphanumeric characters, like
6y
:Input Number: 6y
will result in :The number is: 6 Input Number: That wasn't a number: y
the program reads the input character by character, when it finds a number character in the input, it thinks the input is a number, and when it finds a non numeric one it thinks it is not a number, but can not decide that6y
is not a number altogether, and of course in the process due to the[Enter]
key being still present in the buffer, the same problem arises. -
ilgaar over 8 yearsThis still results in multiple input lines, try
4t
andt4
,4t
will give you-> Err. . .
andt4
will not even give you any errors, but still multiple input lines:-> ->
-
M.M over 8 years"
scanf
is considered a broken function" - well, it is difficult to use, butsscanf
also shares most of the same difficulties. In both cases, use very carefully. -
KriptSkitty over 7 yearsIt works on Linux now (or I should say glibc). It didn't before, and I don't know when they changed it. But the last time I tried it on a mac it crashed, and it is not in the standard, so I've added a warning about portability to this answer.
-
DrBeco almost 7 yearsNot working for me here with my current version.
$ ldd --version
givesldd (Debian GLIBC 2.19-18+deb8u9) 2.19
. That should give all info needed. Anyone has a clue why? -
David C. Rankin over 6 years
fflush
input stream is only defined for input streams associated with seekable files (e.g., disk files, but not pipes or terminals). POSIX.1-2001 did not specify the behavior for flushing of input streams, POSIX.1-2008 does, but only in the limited way described. -
ilgaar over 5 yearsusing
fflush(stdin)
will cause undefined behavior and is not guaranteed to work portably. -
ilgaar over 5 yearsThe above complication in the
while
statement's condition is unnecessary. -
ilgaar over 5 yearssuggesting to use
getline()
will break portability of the code. -
melpomene over 5 years
scanf("%s", &c)
is a type error.%s
takes achar *
, not achar (*)[5]
. Also, since you're not limiting the number of characters read, this is a buffer overflow waiting to happen. Simply discarding the input would be a much better idea (%*s
). -
melpomene over 5 years
gets
is horribly unsafe and should never be used (it has been removed from standard C for that reason). -
midnightCoder over 5 yearsi agree it is dangerous friend and i only used it here for a small application (hence the subjective 40 char array). if the problem at hand were more objective in its requirements then ya ^^.
-
ilgaar over 5 years@M.M
scanf()
is not a broken function, if an op doesn't know howscanf()
works and how to use it, then op probably hasn't read the manual forscans()
andscanf()
cannot be blamed for that. -
M.M over 5 yearsmy text in quotes is quoted text from another comment which has since been removed
-
ilgaar over 5 yearsYou better put the
getchar()
just before the printf("Err. . .\n"); -
Filippo Costa over 4 years@ilgaar What do you mean? It looks fine to me.
-
Steve Summit almost 3 yearsreturning the number of characters consumed -- Actually,
scanf
returns the number of fields successfully read. Soscanf("%d")
might return 1, 0, or EOF. -
Jonathan Leffler over 2 yearsIf you use
getline()
to read a line, you must remember tofree()
the space (or, at least, consider whether it needs freeing, and the answer will usually be "yes" so you end up freeing it). Note thatgetline()
typically allocates space even if the first operation returns-1
(indicating EOF; note that it explicitly returns-1
and notEOF
, even thoughEOF
is usually-1
).