2
#include<stdio.h>
#include<ctype.h>
int main()
{
    int number, sum = 0;

    // loop body is executed at least once
    do
    {
        printf("Enter a number: ");
        scanf("%d", &number);
        sum += number;
    }
    while(number != 0);

    printf("Sum = %d",sum);

}

This code was supposed to print the sum of all the integers entered by the user. But when the user enters a letter as an input, instead of throwing an error, it was going into an infinite loop, printing the "enter a number" statement but not running any other statement in the loop.

Can someone tell me why this happens?

rohit raj
  • 23
  • 2
  • 4
    Use the return value of [**`scanf`**](https://en.cppreference.com/w/c/io/fscanf) to your advantage rather than ignoring it. – WhozCraig Aug 07 '18 at 04:46
  • You're also better off using `strtol()` here – Shahe Ansar Aug 07 '18 at 04:49
  • `scanf` won't "throw an error" when it encounters invalid input. It returns the number of receiving arguments successfully assigned, and it's your job to check that return value. – BessieTheCookie Aug 07 '18 at 04:56
  • 1
    For clarity, please clearly state the *intended* behavior in future questions. You clearly didn't want an infinite loop. But you left to the imagination of the readers whether you wanted the input stream trimmed of bad data, or simply breaking the loop on erroneous input. One of these is simple (the answer you chose); the other considerably more elaborate. Either would have been acceptable answers unless you clarify your desired behavior. – WhozCraig Aug 07 '18 at 05:14
  • 1
    Another duplicate [Why is scanf() causing infinite loop in this code?](https://stackoverflow.com/questions/1716013/why-is-scanf-causing-infinite-loop-in-this-code?rq=1), and there are at least 50 more. You may also want to pay careful attention to the discussion here [Is populating an integer array the same as populating a float one?](https://stackoverflow.com/questions/50167324/is-populating-an-integer-array-the-same-as-populating-a-float-one/50168033#50168033) – David C. Rankin Aug 07 '18 at 05:39

5 Answers5

2

scanf is weird. It has an internal buffer, and will only read from the stream if that buffer is empty. At the start of your code, there is nothing, so a line is read from standard input. Then scanf tries to find an integer there. If an integer is found, it will report it successfully read one item (by returning 1) and puts the read value into the supplied pointer location. However, if an integer is not found, it will return 0 for "zero items successfully read", and it will not consume anything from the buffer. This means, next time scanf is invoked, the buffer will not be empty, consequently the user will not be prompted again for a new line, and scanf will try again to read an integer at exactly the same place it tried last time (and will fail for exactly the same reason). Thus, infinite loop.

To illustrate this "buffer" thing, try this:

scanf("%d", &a);
scanf("%d", &b);
printf("a: %d, b: %d\n", a, b);

and enter 12 53 in one line, as the input to the first scanf. Magically, a ends up as 12 and b ends up as 53. The explanation for the magic is - the first scanf found the buffer empty, read the line from the user, and found one integer, as it was told to; the second scanf found the buffer with some stuff still there, and continued reading where it left off last without having to ask the user for a new line.

As others commented already, it is usually better to read a line yourself (fgets) and parse it yourself (strtol), or if you absolutely need to use scanf, make sure to check if scanf read what you expected it to by inspecting its return code. See here on how to clear the input buffer if scanf fails.

Amadan
  • 169,219
  • 18
  • 195
  • 256
  • 1
    The 'internal buffer' is not really associated with `scanf()` per se, but rather with the `stdin` stream which is read by `scanf()`. – Jonathan Leffler Aug 07 '18 at 07:04
0

scanf() does not discard input not matching the format specifier. You should read it in a char[] with %s and then do a atoi() for trying to convert it to string. If it fails, ERRNO will be set to some error code. That way, you'll not leave any unprocessed input behind if something other than a digit is entered.

Tanveer Badar
  • 4,541
  • 2
  • 27
  • 31
0

A proper fix to your code would be the below snippet. Check if scanf() was successful and then proceed

if (scanf("%d", &number) != 1)
{
        break;
}
Meraj Hussain
  • 185
  • 2
  • 18
0

One of the possible solutions is adding input buffer cleaning code after scanf, e.g. loop with getchar:

#include<stdio.h>
#include<ctype.h>
int main()
{
    int number = -1; // any number not equal 0, to prevent loop break 
                     // after wrong input at the first iteration 
    int sum = 0;
    int c; // char to read input
    // loop body is executed at least once
    do
    {
        printf("Enter a number: ");
        if(scanf("%d", &number) == 1) {
            sum += number; // add if read was successful 
        }
        // buffer clean
        while ((c = getchar()) != '\n' && c != EOF);
    }
    while(number != 0);
    printf("Sum = %d",sum);
}

Also known options for input buffer clean:

  1. fseek(stdin,0,SEEK_END);
  2. fflush(stdin); (for some compilers and OS it works)
VolAnd
  • 6,034
  • 3
  • 19
  • 40
  • 1
    Your `while` loop termination condition should include `c` reaching EOF due to a previously failed attempt. Otherwise, you *still* have an infinite loop. Once `stdin` hits EOF, you will effectively skip the `scanf` (failure on inception), and hit the inner while, which will set `c` to `EOF` again. To that, this also potentially evaluates `number` as indeterminate. If the *first* `scanf` fails `number` was never determined. That, in turn, invokes *undefined behavior*. – WhozCraig Aug 07 '18 at 05:01
  • @WhozCraig Wo you mean just `while((getchar())!=EOF);` ? No, it is not enough, so I use `while ((c = getchar()) != '\n' && c != EOF);` – VolAnd Aug 07 '18 at 05:03
  • @WhozCraig As for initialization of `number`, I agree and snippet was updated - now `int number = -1` before loop starts – VolAnd Aug 07 '18 at 05:09
  • 1
    And `fflush(stdin)` is non-standard. The standard only defines behavior for `fflush` invoked on output streams. Not going to lie, I've never even *heard* of someone trying to perform a `fseek` on `stdin`, but it harbors defined be behavior I'll be surprised. – WhozCraig Aug 07 '18 at 05:10
  • 2
    If `if(scanf("%d", &number))` returns `EOF`, you'll have an infinite loop. I suggest `if(scanf("%d", &number) == 1)` – Spikatrix Aug 07 '18 at 05:11
  • @CoolGuy Why infinite loop? Are you considering case of broken i/o at all, when `scanf` always returns EOF? – VolAnd Aug 07 '18 at 05:15
  • 1
    @VolAnd that's precisely the case he's talking about, but it is overall worse than that. If `scanf` returns EOF **ever** it will *always* do so. The trailing flush-loop will be short-lived (one iteration in fact, reaffirming `c` being EOF), and since `number` hasn't changed, the loop goes on. – WhozCraig Aug 07 '18 at 05:30
  • @WhozCraig Question was about case `"when the user enters a letter as an input"` and in that case `scanf` returns `0` and in my answer `if(scanf("%d", &number))` equal to `if(0 != scanf("%d", &number))`, and yes, `number` will be unchanged if `scanf` do not change it. "Infinite loop" issue solved in my example, and I leave logic for stopping loop as it was - "until the user enters zero" - `while(number != 0);` – VolAnd Aug 07 '18 at 05:44
  • 2
    Then let me put it another way, the OP's code is broken as well (obviously, otherwise they wouldn't be here). They didn't account for *any* return result from `scanf`. With one format specifier, there are three possible outcomes: 0, 1, or EOF. The solution is to account for *all* of them. Your code treats the latter two as equivalent; they're *not*. It's a simple fix, so I'm unclear why you don't just do it so I can uptick your answer. – WhozCraig Aug 07 '18 at 05:54
  • Accepded and condition for `if` now updated – VolAnd Aug 07 '18 at 07:17
0

This question has already been answered here.

Quoting from the answer:

The %d conversion specifier expects the input text to be formatted as a decimal integer. If it isn't, the conversion fails and the character that caused the conversion to fail is left in the input stream. Further calls to scanf() with the %d conversion specifier will choke on the same character.

This basically means that when you call scanf() with %d as the conversion specifier, it'll expect the user to enter a decimal integer. However, when the user enters a character (%c) as an input, the conversion fails and the entered character stays in the input stream. This will cause the while loop to go on forever without allowing the user to enter any more inputs.

To prevent this, you can try something like:

while (x != 0){
  int tmp;
  if (scanf("%d", &tmp) == 0)
    getchar();
  else
    number = tmp;
}