Why is scanf() causing infinite loop in this code?

CGccScanf

C Problem Overview


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 questionscanf() skips every other while loop in C and skimmed through other questions, but nothing really answer this specific problem.

C Solutions


Solution 1 - C

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.

http://www.cs.utah.edu/~zachary/isp/tutorials/io/io.html">Here</a> is a pretty good overview of scanf.

Solution 2 - C

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 - C

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 - C

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

Solution 5 - C

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 6 - C

I had similar problem. I solved by only using scanf.

Input "abc123<Enter>" to see how it works.

#include <stdio.h>
int n, num_ok;
char c;
main() {
    while (1) {
        printf("Input Number: ");
        num_ok = scanf("%d", &n);
        if (num_ok != 1) {
            scanf("%c", &c);
            printf("That wasn't a number: %c\n", c);
        } else {
            printf("The number is: %d\n", n);
        }
    }
}

Solution 7 - C

On some platforms (especially Windows and Linux) you can use fflush(stdin);:

#include <stdio.h>

int main(void)
{
  int number, p = 0, n = 0;

  while (1) {
    printf("-> ");
    if (scanf("%d", &number) == 0) {
        fflush(stdin);
        printf("Err...\n");
        continue;
    }
    fflush(stdin);
    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;
}

Solution 8 - C

The Solution: You need to add fflush(stdin); when 0 is returned from scanf.

The Reason: It appears to be leaving the input char in the buffer when an error is encountered, so every time scanf is called it just keeps trying to handle the invalid character but never removing it form the buffer. When you call fflush, the input buffer(stdin) will be cleared so the invalid character will no longer be handled repeatably.

You Program Modified: Below is your program modified with the needed change.

#include <stdio.h>

int main()
{
    int number, p = 0, n = 0;

    while (1) {
        printf("-> ");
        if (scanf("%d", &number) == 0) {
            fflush(stdin);
            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;
}

Solution 9 - C

I had the same problem, and I found a somewhat hacky solution. I use fgets() to read the input and then feed that to sscanf(). This is not a bad fix for the infinite loop problem, and with a simple for loop I tell C to search for any none numeric character. The code below won't allow inputs like 123abc.

#include <stdio.h>
#include <ctype.h>
#include <string.h>

int main(int argc, const char * argv[]) {
    
    char line[10];
    int loop, arrayLength, number, nan;
    arrayLength = sizeof(line) / sizeof(char);
    do {
        nan = 0;
        printf("Please enter a number:\n");
        fgets(line, arrayLength, stdin);
        for(loop = 0; loop < arrayLength; loop++) { // search for any none numeric charcter inisde the line array
            if(line[loop] == '\n') { // stop the search if there is a carrage return
                break;
            }
            if((line[0] == '-' || line[0] == '+') && loop == 0) { // Exculude the sign charcters infront of numbers so the program can accept both negative and positive numbers
                continue;
            }
            if(!isdigit(line[loop])) { // if there is a none numeric character then add one to nan and break the loop
                nan++;
                break;
            }
        }
    } while(nan || strlen(line) == 1); // check if there is any NaN or the user has just hit enter
    sscanf(line, "%d", &number);
    printf("You enterd number %d\n", number);
    return 0;
}

Solution 10 - C

try using this:

if (scanf("%d", &number) == 0) {
        printf("Err...\n");
        break;
    }

this worked fine for me... try this.. the continue statement is not appropiate as the Err.. should only execute once. so, try break which I tested... this worked fine for you.. i tested....

Solution 11 - C

When a non-number is entered an error occurs and the non-number is still kept in the input buffer. You should skip it. Also even this combination of symbols as for example 1a will be read at first as number 1 I think you should also skip such input.

The program can look the following way.

#include <stdio.h>
#include <ctype.h>

int main(void) 
{
	int p = 0, n = 0;

    while (1)
    {
    	char c;
    	int number;
    	int success;
    
    	printf("-> ");

    	success = scanf("%d%c", &number, &c);
    
		if ( success != EOF )
		{
			success = success == 2 && isspace( ( unsigned char )c );
		}
		
    	if ( ( success == EOF ) || ( success && number == 0 ) ) break;
    
    	if ( !success )
    	{
    		scanf("%*[^ \t\n]");
    		clearerr(stdin);
    	}
    	else if ( number > 0 )
    	{
    		++p;
    	}
    	else if ( number < n )
    	{
    		++n;
    	}
    }
	
	printf( "\nRead %d positive and %d negative numbers\n", p, n );

	return 0;
}

The program output might look like

-> 1
-> -1
-> 2
-> -2
-> 0a
-> -0a
-> a0
-> -a0
-> 3
-> -3
-> 0

Read 3 positive and 3 negative numbers

Solution 12 - C

To solve partilly your problem I just add this line after the scanf:

fgetc(stdin); /* to delete '\n' character */

Below, your code with the line:

#include <stdio.h>

int main()
{
    int number, p = 0, n = 0;

    while (1) {
        printf("-> ");
        if (scanf("%d", &number) == 0) {
            fgetc(stdin); /* to delete '\n' character */
            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;
}

But if you enter more than one character, the program continues one by one character until the "\n".

So I found a solution here: https://stackoverflow.com/questions/28297306/how-to-limit-input-length-with-scanf/28297894#28297894

You can use this line:

int c;
while ((c = fgetc(stdin)) != '\n' && c != EOF);

Solution 13 - C

// all you need is to clear the buffer!

#include <stdio.h>

int main()
{
    int number, p = 0, n = 0;
    char clearBuf[256]; //JG:
    while (1) {
        printf("-> ");
        if (scanf("%d", &number) == 0) {
            fgets(stdin, 256, clearBuf); //JG:
            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;
}

Solution 14 - C

Flush the input buffer before you scan:

while(getchar() != EOF) continue;
if (scanf("%d", &number) == 0) {
    ...

I was going to suggest fflush(stdin), but apparently that results in undefined behavior.

In response to your comment, if you'd like the prompt to show up, you have to flush the output buffer. By default, that only happens when you print a newline. Like:

while (1) {
    printf("-> ");
    fflush(stdout);
    while(getchar() != EOF) continue;
    if (scanf("%d", &number) == 0) {
    ...

Solution 15 - C

Hi I know this is an old thread but I just finished a school assignment where I ran into this same problem. My solution is that I used gets() to pick up what scanf() left behind.

Here is OP code slightly re-written; probably no use to him but perhaps it will help someone else out there.

#include <stdio.h>

    int main()
    {
        int number, p = 0, n = 0;
        char unwantedCharacters[40];  //created array to catch unwanted input
        unwantedCharacters[0] = 0;    //initialzed first byte of array to zero

        while (1)
        {
            printf("-> ");
            scanf("%d", &number);
            gets(unwantedCharacters);        //collect what scanf() wouldn't from the input stream
            if (unwantedCharacters[0] == 0)  //if unwantedCharacters array is empty (the user's input is valid)
            {
                if (number > 0) p++;
                else if (number < 0) n++;
                else break; /* 0 given */
            }
            else
                printf("Err...\n");
        }
        printf("Read %d positive and %d negative numbers\n", p, n);
        return 0;
    }

Solution 16 - C

Good evening. I've recently been through the same problem and i found a solution that might help a lot of guys. Well, actually the function "scanf" leaves a buffer at memory ... and that's why the infinite loop is caused. So you actually have to "store" this buffer to another variable IF your initial scanf contains the "null" value. Here's what i mean:

#include <stdio.h>
int n;
char c[5];
main() {
    while (1) {
        printf("Input Number: ");
        if (scanf("%d", &n)==0) {  //if you type char scanf gets null value
            scanf("%s", &c);      //the abovementioned char stored in 'c'
            printf("That wasn't a number: %s\n", c);
        }
		else printf("The number is: %d\n", n);
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser208785View Question on Stackoverflow
Solution 1 - CjergasonView Answer on Stackoverflow
Solution 2 - CTeddyView Answer on Stackoverflow
Solution 3 - CLucasView Answer on Stackoverflow
Solution 4 - CEli BenderskyView Answer on Stackoverflow
Solution 5 - CpmgView Answer on Stackoverflow
Solution 6 - CManel GuerreroView Answer on Stackoverflow
Solution 7 - CnanotexnikView Answer on Stackoverflow
Solution 8 - CCabbage ChampionView Answer on Stackoverflow
Solution 9 - CilgaarView Answer on Stackoverflow
Solution 10 - CKrishna ChaliseView Answer on Stackoverflow
Solution 11 - CVlad from MoscowView Answer on Stackoverflow
Solution 12 - CsaltView Answer on Stackoverflow
Solution 13 - CJin GuoView Answer on Stackoverflow
Solution 14 - CAndomarView Answer on Stackoverflow
Solution 15 - CmidnightCoderView Answer on Stackoverflow
Solution 16 - CDrunk KoalaView Answer on Stackoverflow