Module 3 · Constants, Qualifiers, Input and Output
When Input Goes Wrong: Checking What You Read
In this lesson
- Read
scanf's returned count as the only honest answer to "did that work". - Show that a failed read leaves your variable alone and the bad character in the stream.
- Print a range check as 1 or 0, and name where the branch that acts on it is taught.
Maria's till asks for an amount and somebody types twelve. The till prints a receipt anyway. The number on it is not twelve and it is not zero. She asks where it came from, and nobody in the shop can tell her.
This lesson is one idea from end to end. scanf already told the program that nothing was read, and the program threw that away. Everything below is what happens next, and what you do instead.
The returned value is a count, and EOF is not a count
Three answers come back from a read of one value, and they mean three different things.
| Returned | What happened | What is in your variable |
|---|---|---|
1 | the item was read and stored | the value from the input |
0 | there was input, and it did not match | whatever was there before |
-1 | the input ran out first; this is EOF | whatever was there before |
The first two are easy to confuse and the difference matters. A 0 means a word arrived where a number was expected, and that word is still waiting. A -1 means nothing arrived at all, and nothing ever will.
EOF is a macro from <stdio.h>. On this toolchain it is -1, which is why a program that prints the returned value prints -1 at the end of its input.
With several specifiers the count goes up to the number of items filled, and it stops at the first one that fails. A three specifier read returning 2 means the third one did not happen.
A failed read leaves your variable exactly as it was
This is the part nobody is told, and it is what makes Maria's receipt so confusing.
When a read fails, scanf does not zero the variable, does not clear it, and does not touch it. Whatever value the variable held before the call, it still holds after.
#include <stdio.h>
int main(void)
{
int amount = -1;
int read = scanf("%d", &amount);
printf("scanf returned %d\n", read);
printf("amount is now %d\n", amount);
return 0;
}
scanf returned 0
amount is now -1
That output is for the input twelve. The -1 on the second line is the -1 from the declaration, untouched.
Run the same program on the input 42 and it prints 1 and 42. The variable is the same variable; only the count told you which of the two stories happened.
So a sensible starting value is not tidiness, it is the answer your program will print when the read fails. Choose it on purpose.
The character that failed is still in the stream
The second half of the surprise. A failed %d does not consume the text that did not match. The t of twelve is still the next character waiting.
So the next %d meets the same t, fails in the same way, and returns 0 again. A program that retries without clearing the stream retries forever.
#include <stdio.h>
int main(void)
{
int amount = -1;
char stuck = '?';
int first = scanf("%d", &amount);
int second = scanf("%d", &amount);
int third = scanf(" %c", &stuck);
printf("first read returned %d\n", first);
printf("second read returned %d\n", second);
printf("third read returned %d and got [%c]\n", third, stuck);
printf("amount is still %d\n", amount);
return 0;
}
first read returned 0
second read returned 0
third read returned 1 and got [t]
amount is still -1
That output is for the input twelve. Read the third line: the character the two number reads choked on is the letter t, and a %c picks it up happily.
That is the proof. The stream never moved, because %d only consumes characters it can use.
Clearing the rest of the bad line properly needs a loop, which is Module 6. Reading one bad character out of the way, as above, is what you can do today.
A range check without an if: print the comparison
Knowing the read worked is half of checking input. The other half is whether the value makes sense, and that is a comparison.
Module 2 lesson 4 said a comparison is an expression worth 1 or 0. That is all you need to report a check, even though acting on it is Module 5's job.
Joining two checks with && is Module 4, so until then two checks are two lines.
#include <stdio.h>
int main(void)
{
const int LOWEST = 1;
const int HIGHEST = 100;
int marks = 0;
int read = scanf("%d", &marks);
printf("read ok : %d\n", read == 1);
printf("at least %3d : %d\n", LOWEST, marks >= LOWEST);
printf("at most %3d : %d\n", HIGHEST, marks <= HIGHEST);
return 0;
}
read ok : 1
at least 1 : 1
at most 100 : 0
That output is for the input 150. The read worked, the value is above the lowest allowed, and it is not below the highest.
Now run it on twelve and read the answer carefully.
read ok : 0
at least 1 : 0
at most 100 : 1
The third line says the value is inside the band. It is, because the variable still holds the 0 the declaration gave it, and 0 is at most 100.
That is the whole reason the first line exists. A range check on a value that was never read is an answer about your own initialiser. It looks exactly like an answer about the input.
So the order is fixed: ask whether the read worked, and only then ask whether the value is sensible.
fgets, in one paragraph
There is a different way to read input, and it avoids most of this lesson. fgets takes a whole line, spaces and all, into an array of characters, and then you pick it apart yourself.
It never leaves a half read number in the stream, because it always consumes a whole line. Module 10 teaches it properly, once text is a thing you can work with.
Until then, scanf plus its returned count is the honest tool, and it is what every judge problem in this track uses.
What a judge does with wrong output
A judge runs your program on an input it holds, captures everything you printed, and compares it with the expected output character by character.
It does not know what you meant. A value that is right and a line that is one space wrong are the same verdict as a value that is nonsense.
That cuts both ways, and it is why this module has spent two lessons on printing exactly. It also means a program that prints a sensible answer for bad input is not rewarded for it.
On a judge the input is always valid, because the statement promised it. The return value still matters, because the same program run by a person is not so lucky.
One value, one count, two lines of output. Every graded problem in this module starts from this shape.
#include <stdio.h>
int main(void)
{
int n = 0;
int read = scanf("%d", &n);
printf("returned %d\n", read);
printf("value %d\n", n);
return 0;
}
returned 1
value 12
That output is for the input 12. On the input twelve the same program prints 0 and 0, and the second 0 came from the declaration.
A record of three fields where the third is missing. The counts tell you exactly where the input stopped.
#include <stdio.h>
int main(void)
{
int roll = 0;
int marks = 0;
int bonus = 0;
int read = scanf("%d %d %d", &roll, &marks, &bonus);
printf("fields read : %d\n", read);
printf("roll : %d\n", roll);
printf("marks : %d\n", marks);
printf("bonus : %d\n", bonus);
return 0;
}
fields read : 2
roll : 47
marks : 88
bonus : 0
That output is for the input 47 88. Two fields filled, the third untouched, and the 0 it prints is the declaration's 0 rather than a missing mark.
One returned number told you which fields to trust. Nothing else in the program could have.
Run in CompilerThe read, the band, and a single line that is 1 only when everything is right. The multiplication is standing in for the && that Module 4 brings.
#include <stdio.h>
int main(void)
{
const int LOWEST = 0;
const int HIGHEST = 100;
int marks = -1;
int read = scanf("%d", &marks);
printf("read ok : %d\n", read == 1);
printf("in range : %d\n", (marks >= LOWEST) * (marks <= HIGHEST));
printf("usable : %d\n", (read == 1) * (marks >= LOWEST) * (marks <= HIGHEST));
return 0;
}
read ok : 1
in range : 1
usable : 1
That output is for the input 88. On the input twelve the three lines read 0, 0 and 0, because the -1 in the declaration is below the lowest allowed value.
That -1 was chosen for exactly that reason. A starting value of 50 would have made the middle line say 1 for an input that was never read.
Run in CompilerWhere this is used
- A web form's server side check. The browser check is a convenience and the server repeats every one of them, because the browser can be bypassed. The shape is identical: did the field arrive, and is the value in range.
- A configuration loader at start up. A program that reads a settings file and finds a port number of
eightyshould refuse to start, not start on port 0. The count is how it knows. - A bank's transaction import. Each record is read, counted and range checked, and a record that fails goes to a reject file with its line number. Nothing is guessed at, because a guessed amount is somebody's money.
- The judge behind this track's problems. It reads your program's output with the same care. A line that is one character out is a wrong answer, not a near miss.
Common mistakes
1. Ignoring the returned count.
int amount = 0;
scanf("%d", &amount);
printf("total: %d\n", amount * 2);
No message at either command line, and the program is perfectly legal. On the input twelve it prints total: 0 with complete confidence. The fix is to keep what scanf returned and print it, which is what every graded starter in this module does.
2. Assuming a failed read zeroes the variable.
int marks;
scanf("%d", &marks);
printf("%d\n", marks);
Neither the Playground nor a local build with -Wall says anything here: the address of marks went to scanf, so the compiler assumes the read wrote it. If the read fails, marks is whatever was in that memory, which Module 2 lesson 1 showed is not a promise of zero.
3. Reading again after a failure, with nothing cleared.
int n = 0;
scanf("%d", &n);
scanf("%d", &n);
No message, and both reads return 0 on bad input. The offending character was never consumed, so the second read meets it too. A retry that does not first remove the bad input is not a retry.
4. Checking the range before checking the read.
int marks = 0;
scanf("%d", &marks);
printf("%d\n", marks <= 100);
No message, and it prints 1 for the input twelve. The check is true, about a 0 that came from the declaration rather than from anybody's input. Ask whether the read worked first, always.
Report what a single read actually did: the count it returned, and the value in the variable afterwards.
Input. At most one line holding one token, either an integer or a word. It may be empty.
Output. Two lines: returned and the count, then value and the variable.
Constraints. An integer is between -1000000 and 1000000, a word is 1 to 20 letters, and the variable starts at 0.
Sample. Input twelve gives returned 0 then value 0.
#include <stdio.h>
int main(void)
{
int amount = 0;
int read = scanf("%d", &amount);
/* Two lines. Print what scanf gave back, then what is in the box. */
return 0;
}
Graded as read-report. Two hidden tests make the point: the input 0 prints the same second line with a different first line, and empty input prints -1.
Zara's sensor is trusted only inside a band. Print the two facts and let a person read them.
Input. One line with three integers: the lowest allowed, the highest allowed, and the reading.
Output. Two lines, each 1 or 0: at least the lowest, then at most the highest.
Constraints. -1000000 <= lowest <= highest <= 1000000. No if and no &&.
Sample. Input 1 100 50 gives 1 then 1.
#include <stdio.h>
int main(void)
{
int low = 0;
int high = 0;
int reading = 0;
scanf("%d %d %d", &low, &high, &reading);
/* Two lines, each a comparison printed with %d. No if anywhere. */
return 0;
}
Graded as in-range. Both ends of the band are hidden tests, and a reading equal to a bound is inside it.
Read two numbers where the second one may not be there at all, and print both, with the second showing 0 when it was missing.
Input. One or two integers, separated by whitespace.
Output. Three lines: the count, the first value, the second value.
Do it without an if. The 0 for a missing second value is not something you write, it is something you choose in the declaration.
#include <stdio.h>
int main(void)
{
int a = 0;
int b = 0;
int read = scanf("%d %d", &a, &b);
/* Three printf calls. Nothing decides anything. */
return 0;
}
Not graded in this module; its graded relative is line-parser in the problems lesson, which does this three times over. Run it on one number, on two, and on none.
Common doubts
If the input on a judge is always valid, why check it?
Because the habit is cheap and the alternative is invisible. A program you write for a person, or for a file somebody else produced, is where this stops being an exercise.
How do I clear the bad input and try again?
You read characters until you reach the end of the line, and that needs a loop. Module 6 gives you the loop and comes back to this exact problem.
Is
EOFalways -1?The standard only says it is a negative integer constant. It is -1 on the Playground and on every compiler this course runs on, and writing
EOFrather than -1 is what keeps that from mattering.Why do the starters in this module initialise every variable?
So that a failed read has a defined answer. It is the same rule as Module 2 lesson 1, with a second reason: here the initialiser is what the program will print.
Can I print a message when the read fails?
Not without a branch, which is Module 5. Today the honest thing is to print the count, which tells a reader the same fact in one number.
Key takeaways
scanfreturns how many items it filled: a count, withEOFfor input that ran out.- A failed read leaves your variable untouched, so its starting value is what you will print.
- The character that failed to match is still in the stream, so an immediate retry fails the same way.
- A comparison is worth 1 or 0, so a range check can be printed without an
if. - Check that the read worked before checking the value, or you are checking your own initialiser.
- A judge compares your output character by character and does not know what you meant.
Next you put all five lessons together on ten graded problems, where the judge is the only opinion that counts.
End of lesson 5
Mark it done, and your progress moves with you.
Next: Problems: Reading and Printing