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:
scanf()
paired with%c
as its format string will NOT automatically skip leading whitespace characters (' '
,\n
,\t
,\r
) in thestdin
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 byscanf()
and assigned to variables specified during thescanf()
call.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.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 thestdin
buffer of leftover "garbage" immediately after some keyboard operation. For example, in the code above, after ANYscanf()
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);
}