0

The C syntax question is: does C execute printf() statements past scanf() statements below it? I have learned about the concept of side effects and sequence points. Could side effects of scanf() be delayed behind the side effects of printf() which are actually further down the code?

To illustrate the question above, here is some context: I am working on a simple guessing game C program, where the program tries to guess the number the user has in mind between 1-100. Currently, my program looks like this and is erroneous:

#include <stdio.h>
#include <stdbool.h>    // bool

int higherBound = 100;  // global variables for nextGuess
int lowerBound = 1;

int nextGuess(int prevGuess, bool isHigher)
{
    /*if the feedback is that truth isHigher, then the program
    takes the mid point between prevGuess and higherBound. Otherwise,
    take the mid point between prevGuess and lowerBound.*/
    int newGuess;
    if (isHigher){
        newGuess = (prevGuess + higherBound) / 2;   // integer divide will truncate
    } else {
        newGuess = (prevGuess + lowerBound) / 2;
    }
    return newGuess;
}

int main(void)
{
    int guess = 50;
    printf("Pick an integer from 1 to 100. I will try to guess ");
    printf("it.\nRespond with a y if my guess is right and with");
    printf("\nan n if it is wrong.\n");
    printf("Uh...is your number %d?\n", guess);
    bool isHigher = false;
    char answerToGuess;
    char answerToHigher;
    scanf("%c",&answerToGuess);
    while (answerToGuess != 'y'){
        answerToHigher = ' ';   // reset user answer
        do {
            printf("Okay, is your number higher than my guess? (y/n)\n");
            scanf("%c", &answerToHigher);
        } while (answerToHigher != 'y' && answerToHigher != 'n');
        if (answerToHigher == 'y'){
            isHigher = true;
            lowerBound = guess; // update lower bound
        }
        else if (answerToHigher == 'n'){
            isHigher = false;
            higherBound = guess;
        }
        printf("Well, then, is it %d?\n", guess = nextGuess(guess, isHigher));
        scanf("%c",&answerToGuess);
    }
    printf("I knew I could do it!\n");
    return 0;
}

When I run it, what I did not expect to happen was that the line Okay, is your number higher than my guess? always gets printed twice (say the number I have in mind is 60) as if the side effect of printf() penetrated the scanf() line atthe end of the outer while loop:

Pick an integer from 1 to 100. I will try to guess it.
Respond with a y if my guess is right and with        
an n if it is wrong.
Uh...is your number 50?
n
Okay, is your number higher than my guess? (y/n)
Okay, is your number higher than my guess? (y/n)
y
Well, then, is it 75?
Okay, is your number higher than my guess? (y/n)
n
Well, then, is it 50?
Okay, is your number higher than my guess? (y/n)
n
Well, then, is it 50?
Okay, is your number higher than my guess? (y/n)
Okay, is your number higher than my guess? (y/n)

So what is the reason that this line gets printed twice every time?

I have tried to debug using the VS code built-in debugger, but the behavior is different from the terminal execution.

EDIT: problem solved thanks to a suggestion by @Retired_Ninja: add a whitespace inside the format strings of scanf(). So after I modify scanf("%c", &answerToHigher); into scanf("% c", &answerToHigher); and modify scanf("%c", &answerToGuess); into scanf("% c", &answerToGuess);, the program runs as intended.

The root cause, however, is not what I thought. This problem has nothing to do with side effects and sequence points. The root cause was that scanf() took something left over in the buffer (the \n from previous scanf() operations), and it looks as if scanf() was skipped. It was not; it was simply consuming the \n's "hidden" from my eyes and awareness.

The problem could have been avoided if I had followed some of the best practices for using scanf() or, in general, using stdin buffer. The scanf() best practice is that you should check the return value of scanf() and make sure it is not zero (zero indicates matching failure).

My elevated understanding:

  1. scanf() paired with %c as its format string will NOT automatically skip leading whitespace characters (' ', \n, \t, \r) in the stdin buffer. As a result, if there are indeed leftover whitespaces from previous keyboard inputs (such as \n which are very common), then these whitespaces will be accidentally taken by scanf() and assigned to variables specified during the scanf() call.

  2. This not-automatically-skipping-leading-whitespace behavior happens only with %c, %n, %[scanset] format strings. For other format strings (e.g., %d, %f), scanf() WILL automatically skip leading whitespaces.

  3. Best practice: in fact, the best practice is NOT what the answer suggested (using "% c" instead of "%c). The best practice is to always clean up the stdin buffer of leftover "garbage" immediately after some keyboard operation. For example, in the code above, after ANY scanf() call, there should be a BUFFER-CLEANUP section of code:

// consume all trailing buffer contents up to a '\n' character
// (including the '\n') and dispose of them from the buffer
while (getchar() != '\n')
    continue;

For double security, you can combine the above buffer-cleaning code with scanf(" %c", &ch);. More typing but this gets you double safety.

Another best practice (often not followed) is to always check the return value of scanf() and verify if the variable gets assigned (i.e., verify if matching failure occured):

char ch;
if (scanf("%c", &ch) == 0){
    perror("scanf() failed to assign value.");
    exit(-1);
}
Tong Zhao
  • 91
  • 7
  • 1
    Add a space before `%c` in your format strings to consume leading whitespace. So `scanf(" %c", ...` – Retired Ninja Dec 25 '22 at 16:50
  • @RetiredNinja, you hit the jackpot. I fixed the code immediately after adding a leading ' ' (space) in the format strings of both my `scanf()` statements. – Tong Zhao Dec 25 '22 at 17:52
  • @RetiredNinja but I also found a better practice, which is to make sure that after each `scanf()` operation involving `%c` which could potentially leave whitespaces in the buffer, one should perform a buffer-cleanup operation using something like `while (getchar() != '\n') continue;`. This also worked. It takes more lines, but is more intuitive and easier to remember. – Tong Zhao Dec 25 '22 at 18:33
  • 1
    The effect of `printf` is to put characters into an output buffer. It does not include the effect of passing the contents of the buffer to the host environment. That action happens automatically in some circumstances, such as the output stream being in a "line buffered" mode and a newline `\n` character is printed. Or when the buffer is full. You have to call `fflush(stdout)` if you require all the recently printed characters to be sent to the system (e.g. because the user needs to see a prompt before entering input, and the prompt isn't terminated by a newline). – Kaz Dec 25 '22 at 19:03
  • 1
    `scanf` should never be used for interactive user input. – Kaz Dec 25 '22 at 19:07
  • 1
    @TongZhao You can do that, but it assumes you will only ever have one input per line. If I had `a b c d e f` it would discard a bunch. – Retired Ninja Dec 26 '22 at 09:32

0 Answers0