1

Here is something that confused me.

# include <stdio.h>
# include <limits.h>

int main()
{ 
    int a;
    int b;

    printf("Enter two integers: ");


    while(scanf("%d %d", &a, &b)!=2||b==0||a+b>INT_MAX||a*b>INT_MAX){

        printf("Invalid entry, please re-enter: ");
        while(getchar()=='\n'); 
    }

    printf("sum is %d, difference is %d, product is %lf, divid is %.2f, remainder is %d", a+b, a-b, (double)a*b, (float)a/b, a%b);

    return 0;
}

With above code, if I enter "a 1" press ENTER:

Invalid entry, please re-enter:

pops up, then I enter "2 B" press ENTER, the last printf will execute.

However, if I change while(getchar()=='\n'); to while(getchar()!='\n');, and with the same entry (I enter "a 1" press ENTER)

Invalid entry, please re-enter:

pops up, then I enter "2 B" press ENTER), the last printf will not execute, and

Invalid entry, please re-enter:

pops up again.

What causes the difference here? How exactly do scanf and getchar work?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
John
  • 23
  • 3
  • [this](http://stackoverflow.com/a/3640614/5513005) might help you – Yash Karanke May 16 '17 at 15:58
  • 4
    BTW `Result of the operation of int >INT_MAX` has no meaning. – BLUEPIXY May 16 '17 at 16:01
  • 3
    "How exactly do scanf .... work" The C spec has 8 pages of specification and examples on `fscanf()/scanf()`. You need to narrow the question. – chux - Reinstate Monica May 16 '17 at 16:04
  • 3
    Note: `a+b>INT_MAX` substitute: `(a>INT_MAX-b || b>INT_MAX-a)` Addtion still can overflow the other way against `INT_MIN` yet this issue is likely not so central to OP's concern. – chux - Reinstate Monica May 16 '17 at 16:08
  • 1
    Obviously, what causes the difference is whether you read (with `getchar()`) until the result *differs* from `'\n'` or until it *equals* `'\n'`. Surely that's a non-trivial distinction. – John Bollinger May 16 '17 at 16:09
  • @chux, nice trick! – David Ranieri May 16 '17 at 16:09
  • Think about what you're shoving into your input stream: `a 1\n2 b`. The first `scanf` completely fails, leaving the input stream intact. Your useless `while(getchar()=='\n');` loop pulls one char from the input stream (`a`), and seeing it isn't a newline, breaks. You return to your `scanf` outer loop where there is now `1\n2 b` left on the stream (space preceding). Pulling two `int` works, and the outer loop breaks (leaving a space, a `b`, and a newline). – WhozCraig May 16 '17 at 16:09
  • 1
    @KeineLust [Similar/better tricks](https://codereview.stackexchange.com/a/93699/29485) – chux - Reinstate Monica May 16 '17 at 16:12
  • 1
    Note that the code does not handle EOF gracefully. – Jonathan Leffler May 16 '17 at 16:20
  • `a+b>INT_MAX` How's that going to work? By definition you cannot detect whether something goes above its own range. – Lightness Races in Orbit May 16 '17 at 16:31
  • Questions asking us to recommend or find a book, tool, software library, tutorial or other off-site resource are off-topic for Stack Overflow as they tend to attract opinionated answers and spam. Instead, describe the problem and what has been done so far to solve it. – too honest for this site May 16 '17 at 16:35

4 Answers4

1

scanf() and getchar() read what is in the input buffer (stdin for standard input). When stdin is empty, the program will wait for the user to write something with the keyboard. When you press ENTER, the text you just wrote is stored in stdin. Then scanf() or getchar() can read something in stdin. Notice that pressing ENTER will store a '\n' character (newline) in stdin. A code like while( getchar() != '\n' ); is asking to getchar() to read one character from stdin while the character is not '\n'. It compares the return of getchar() with '\n'. So this is a way to "clean" the input buffer otherwise your program can go crazy.

EDIT

With the code you posted : scanf() is called, you write "a 1", it tries to read a first integer but it is a character ('a') so it stops reading. printf() ask you to re-enter, then getchar() is called and reads the 'a'. Now scanf() is called a second time, reads the 1, then you have to enter something, you enter "2 B" so the second %d in scanf() can read the 2. Now the return of scanf() is 2, b is not 0 and the others conditions are false so the loop is ending. Notice that the 'B' character is still in stdin :)

Community
  • 1
  • 1
Gam
  • 684
  • 1
  • 8
  • 17
  • 1
    I dislike the characterization of `stdin` as an "input buffer". It is a stream (`FILE *`), so it *has* a buffer, but it is not a buffer itself. – John Bollinger May 16 '17 at 16:11
  • In any case, this doesn't answer what seems to be the OP's key question: why does the loop exit in one case but not in the other? – John Bollinger May 16 '17 at 16:12
  • I hope that my edit is clear :) Maybe it is easier to draw with pen/paper to fully understand – Gam May 16 '17 at 16:42
1

How do scanf and getchar work in C?

Sadly, they often do not work together well.


Code is a poor example of attempting to consume the rest of the input line when the input text is a problem.

If scanf("%d %d", &a, &b) failed to scan in 2 int, a prompt occurs and input is read with getchar until an Enter or '\n' happens.

If 2 int were scanned, various faulty tests are performed on a,b

// poor code
while(scanf("%d %d", &a, &b)!=2||b==0||a+b>INT_MAX||a*b>INT_MAX){
  printf("Invalid entry, please re-enter: ");
  while(getchar()=='\n'); 
}

The problem is that the code certainly intends to read a line of user input and then validate it. Unfortunately, scanf("%d %d" can consume multiple '\n' and leaves stdin with unclear contents when 2 int are not scanned. Code is an infinite loop on end-of-file.

Better to read a line of input with fgets(). Flushing stdout insures the prompt is seen before input is read.

char buf[80];
const char *prompt = "Enter two integers: ";
for (;;) {
  fputs(prompt, stdout);
  fflush(stdout);
  if  (fgets(buf, sizeof buf, stdin) == NULL) Handle_EndOfFile_or_Error();
  prompt = "Invalid entry, please re-enter: ";
  int n;
  if (sscanf(buf, "%d%d %n", &a, &b, &n) != 2) continue;  // not 2 ints
  if (buf[n]) continue;  // extra text
  if ((a < 0) ? (b < INT_MIN - a) : (b > INT_MAX - a)) continue;  // + overflow

  ... // other tests

  else break;
}

other tests

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
1

In the first case (while (getchar() == '\n');), the getchar() reads the a, but it isn't a newline, so the loop exits, leaving the space and the 1 in the input buffer. The repeated call to scanf() skips white space, reads 1, skips white space (newline included) and reads 2, leaving space and B in the input. Since the loop condition is now terminated (scanf() returned 2), the printf() is executed.

In the second case (while (getchar() != '\n');), this loop reads the a, the blank, the 1, and the newline before stopping. When you type 2 B, there aren't two numbers, so scanf() returns 1 and the main loop continues.

Note that your code will go into an infinite loop if you indicate EOF (usually Control-D on Unix, Control-Z on Windows) — at least, on most systems. You should always consider EOF. In context, you probably need something like:

int rc;
while (((rc = scanf(…)) != 2 && rc != EOF) || …)

You'd test rc against EOF before printing, too.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • This answers my questions, I did not realize getchar also reads and removes integers (1 is removed in this case). – John May 16 '17 at 16:42
  • One more question, for the code I posted (while (getchar() == '\n'); when I type "a 1 b 2 c 3", "Invalid entry, please re-enter:" only pops for three times, rather than six times. Does it mean the integers are actually skipped by getchar() loop, which might contradict your answer? – John May 16 '17 at 16:47
  • @John: `getchar()` reads characters — any and all characters — and it reads the digit `'1'` (as it would appear in C source code). Strictly, I suppose I should say it reads `char` values or bytes, since it is not aware of multibyte character encodings such as UTF-8 (where a character or grapheme may be encoded by 1-4 bytes), but an awful lot of the time, people from Europe or America say 'character' and mean 'byte' because the two are often interchangeable. – Jonathan Leffler May 16 '17 at 16:47
  • With `while(getchar() == '\n');` loop and input `a 1 b 2 c 3`, the first `scanf()` baulks on the `a` but the loop reads it; the second `scanf()` reads the `1` but baulks on the `b`, then the loop reads the `b`; the third `scanf()` reads the `2` but baulks on the `c`, then the loop reads the `c`; then next `scanf()` reads the `3` and continues to look for another number (or a non-numeric, non-white-space character) before completing — successfully or unsuccessfully depending on what follows the `3`. So, the message should pop up 3 times only — unless you type `d` or something similar after `3`. – Jonathan Leffler May 16 '17 at 16:51
  • With while(getchar() != '\n'), once the code enters the while loop, after "Invalid entry, please re-enter:" I enter 1 2, it will exit the loop and execute the last printf. My question is after I enter 1 2, is scanf checked first or getchar() checked first, my understanding is getchar() should be checked first based on the flow, if this is true, then both 1 and 2 will be removed from the input buffer, then scanf should fail to get the input and the loop should execute one more cycle, which is not true here. – John May 16 '17 at 20:23
  • When you enter `1 2` after the preceding newline has been gobbled up by the `getchar()` loop, then `scanf()` is called, reads the `1`, blank and `2`, leaving the newline after them for later processing (which doesn't happen, but that isn't a problem). Since `scanf()` returns `2` (it converted two numbers successfully), and the other loop criteria are satisfied, the code doesn't go anywhere near `getchar()` again — it just jumps to the `printf()` after the loop. – Jonathan Leffler May 16 '17 at 20:26
  • Yup. Fixed, thanks. That's the trouble with code fragments — they can't be compiled. The unbalanced parentheses would have prevented compilation. – Jonathan Leffler May 16 '17 at 21:37
  • @JonathanLeffler, my question is when in the while scanf loop, if I type a 1 press Enter, I suppose getchar will read and throw away a 1, then read and throw away \n and exit the while(getchar() == '\n' loop, nothing is left in the input buffer, second scanf loop will run, and re-enter comes up again and if I type in 1 2, press Enter, shouldn't it be getchar executed first, which would read and throw away 1, 2 and \n, rather than scanf being executed and two integer being successfully captured? IOW, my question is which one, scanf or getchar, will run right after I type in new inputs. – John May 16 '17 at 22:21
  • You ask about 'the loop', but there are two loops. Which are you referring to? You can edit/clarify your comment for the first 5 minutes. – Jonathan Leffler May 16 '17 at 22:23
  • I'm having difficulty understanding how you are confused. Remember, `scanf()` does not care in the slightest about newlines — newlines are just white space. It does not read a single line on each call. It reads as many lines as necessary, or only part of line if only part of a line is necessary. Let's assume you start the program and type `a 1` (plus enter). The first `scanf()` fails, leaving the `a` in the input buffer. The `while (getchar() != '\n');` loop runs, and reads the letter, the blank, the digit and the newline, terminating the inner loop. The outer loop calls `scanf()` again. … – Jonathan Leffler May 16 '17 at 22:28
  • … The input buffer is empty, so it waits while you type `1 2` (and enter). The `scanf()` recognizes two numbers and leaves the newline in the buffer. It reports that it read 2 values, and the values satisfy the other conditions in the outer while loop condition, so the outer while loop terminates without going near `getchar()` again. `scanf()` usually reads one more character to know that it is finished, and then pushes that character back into the input stream for the next input operation to consume. – Jonathan Leffler May 16 '17 at 22:31
  • @JonathanLeffler My question is here, after getchar consumes all input (a 1 \n), input buffer is empty, so scanf has nothing to read, and "Invalid entry, please re-enter:" shows up, then I type 1 2, press Enter. Here is the point the confuses me: shouldn't it be getchar executed after the input, since it follows right after printf statement. But obviously scanf runs first and reads the input (1 2) before getchar reads and removes the input (1 2) from input buffer. – John May 17 '17 at 14:14
  • No; after the input buffer is empty, `scanf()` waits for new input to arrive and refill the buffer — or it waits until it gets EOF. – Jonathan Leffler May 17 '17 at 14:15
  • @JonathanLeffler You mean regardless of what statement follows the printf statement in the out loop, once there is new input arriving, scanf will run first and reads the input? I thought scanf won't run until all the statements in the loop are executed. – John May 17 '17 at 14:22
  • Once the inner `getchar()` loop completes (e.g. because it has read a newline), then the next I/O function to run is `scanf()` — period. The `getchar()` function doesn't get another crack at the input until after `scanf()` returns. – Jonathan Leffler May 17 '17 at 14:24
0

I try to summarize what you need to know.

First, about your question. You do know the function of scanf is to read what the input (number) we give right? getchar on the other hand, is like scanf except that it is used for character/sentence only. You cannot use getchar to input number.

Second, there are no problem in your code except that it is okay not to use some line like that getchar line. It is also okay not to use while. Unless you want to make some condition, you may combine while with if / else function.

Third, you cannot input the number like you did: "1 a", "2 b". Those are considered invalid. This is because you are using %d in your scanf which refer to decimal number only. It means, you can input number 1-9 only and not the letters. So when the program asked to enter the 2 integers, you just type for example: 4 5. That's 4, space, 5. That is the correct way.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
yek king
  • 1
  • 1
  • Why do you think it is not correct to use a loop such as `int c; while ((c = getchar()) != EOF && c != '\n') { … }`? Such a loop, probably with an empty statement in place of the `{ … }` loop body I just showed, is perfectly fine. The code in the question is similar. It doesn't handle EOF correctly, but you don't identify that as being the problem — you seem to think that it is not permissible to call a function in a loop condition. However, if that's what you're thinking, then you're thinking incorrectly. Also, it is clear that the OP knows that `1 a` and related inputs are incorrect. – Jonathan Leffler May 16 '17 at 20:32
  • Because I try to delete that getchar line, and input the integer as I mentioned in third answer, the code still work and it solve the operation just fine. While on the loop topic, I just suggest to drop the line based on what problem he is trying to solve. But now I just noticed that he is trying to notify the user that if the input is more than 2, it is invalid. My bad. So the loop is necessary here. But I cannot understand the function of getchar here coz he is trying to input the integer and not a string. – yek king May 18 '17 at 10:48