C / CONSOLE INPUT AND OUTPUT
Return values of printf and scanf as error signals
Use printf's character count and scanf's assignment count to detect output errors, partial matches and end of input, and test each one correctly.
What you will learn
- Test output failure with printf(...) < 0, not == 0; the count is characters written
- Compare scanf's return against the number of assigning conversions you asked for
- Read 0 as matching failure and EOF as input failure, and handle them differently
- Treat every variable beyond scanf's returned count as still uninitialized
Understanding Return values of printf and scanf as error signals
printf does not return a status code, it returns the number of characters it actually produced, which is why a successful call hands back a positive number and printf("") legitimately hands back zero. Failure is signalled only by a negative value, so the single correct test is printf(...) < 0. Because stdout is normally buffered, a printf that merely fills a buffer has nothing to fail at yet, so a healthy count means the bytes are queued, not that they reached the terminal or file; a write error can appear later, in fflush or fclose.
scanf returns how many of your variables it managed to fill. It walks the format string strictly left to right and stops at the first thing that does not fit, which produces three distinct signals: the full count means everything you asked for was assigned, a smaller number including zero means it stopped partway and every variable after that point still holds whatever it held before, and EOF means input ran out or a read error occurred before even the first conversion started. The number to compare against is the count of assigning conversions, not the count of percent signs, because %*d consumes without assigning and %n stores without counting.
The two failure numbers are not interchangeable. Zero means the characters are present but the wrong shape, and they are still sitting in the stream unread, which is why while (scanf("%d", &n) != EOF) becomes an infinite loop the moment someone types a letter: 0 != EOF stays true and the letter is never consumed. EOF means there is nothing left, so retrying is pointless. Using the return as a boolean destroys the distinction, since -1 is true and 0 is false, exactly backwards from what you want.
<stdio.h>
int main(void)
{
int a, b, r;
r = printf("hello\n");
if (r < 0) {
fputs("stdout is broken\n", stderr);
return 1;
}
printf("printf returned %d\n", r);
a = b = -1;
r = sscanf("12 34", "%d %d", &a, &b);
printf("[12 34] -> %d (a=%d, b=%d)\n", r, a, b);
a = b = -1;
r = sscanf("12 xy", "%d %d", &a, &b);
printf("[12 xy] -> %d (a=%d, b=%d)\n", r, a, b);
a = b = -1;
r = sscanf("xy 12", "%d %d", &a, &b);
printf("[xy 12] -> %d (a=%d, b=%d)\n", r, a, b);
a = b = -1;
r = sscanf("", "%d", &a);
printf("[] -> %d (a=%d, EOF is %d)\n", r, a, EOF);
return 0;
}
printf and scanf report success as counts rather than status codes, so the returned number tells you how much work got done and precisely where it stopped.
Worked examples
Branching on all three outcomes
Shows the four-way test that separates full success, partial match, matching failure and input failure.
<stdio.h>
static void report(const char *text)
{
int a, b, r;
a = b = 0;
r = sscanf(text, "%d %d", &a, &b);
if (r == 2)
printf("[%s] both fields read: %d and %d\n", text, a, b);
else if (r == 1)
printf("[%s] only the first field read: %d\n", text, a);
else if (r == 0)
printf("[%s] matching failure, first field is not a number\n", text);
else
printf("[%s] input failure, nothing left to read\n", text);
}
int main(void)
{
report("7 8");
report("7 eight");
report("seven 8");
report("");
return 0;
}
Example explained
Line 1r == 2 is the only branch where both a and b may be used, because it is the only one where both were assigned.
Line 2For "7 eight" the first %d succeeds and the second stops on 'e', so r is 1 and b keeps the value it had before the call.
Line 3For "seven 8" the very first conversion fails, so r is 0 and nothing was assigned, yet the characters are still available for another attempt.
Line 4The empty string gives a negative r because input ran out before any conversion could begin, which is the only case where retrying is hopeless.
Conversions that do not count
Demonstrates why the expected return value is the number of assigning conversions, not the number of percent signs.
<stdio.h>
int main(void)
{
int y, m, d, r;
r = sscanf("2026-09-03", "%d-%d-%d", &y, &m, &d);
printf("three assigning conversions -> r=%d\n", r);
y = m = d = 0;
r = sscanf("2026-09-03", "%d-%*d-%d", &y, &d);
printf("middle one suppressed -> r=%d (y=%d, d=%d)\n", r, y, d);
r = sscanf("2026-09-03", "%d-%d", &y, &m);
printf("only two requested -> r=%d\n", r);
r = sscanf("2026/09/03", "%d-%d-%d", &y, &m, &d);
printf("separator mismatch -> r=%d\n", r);
return 0;
}
Example explained
Line 1The first call has three assigning conversions and all three succeed, so full success is r == 3.
Line 2The second format has three conversion specifications but only two assign, because %*d reads the middle number and throws it away, so full success is r == 2 and a test against 3 would report a false error.
Line 3The third call succeeds with r == 2 even though "-03" is left unexamined, so a full count says nothing about how much input remains.
Line 4The last call assigns 2026 and then dies on the literal '-' that cannot match '/', so r == 1 points at a broken separator rather than a badly formed number.
printf's count, and where an output error really appears
Uses the character counts printf returns and checks both printf and the flush for failure.
<stdio.h>
int main(void)
{
const char *names[] = { "ada", "grace", "edsger" };
int total = 0;
int i;
for (i = 0; i < 3; i++) {
int n = printf("%d: %s\n", i, names[i]);
if (n < 0) {
fputs("write to stdout failed\n", stderr);
return 1;
}
total += n;
}
if (fflush(stdout) == EOF) {
fputs("stdout could not be flushed\n", stderr);
return 1;
}
printf("%d characters in %d lines\n", total, i);
return 0;
}
Example explained
Line 1printf returns what it produced, so n is 7, 9 and 10 for the three lines, and summing gives a byte total without measuring the strings by hand.
Line 2The failure test is n < 0, because success is a count: a check like n != 0 would flag every successful call as an error.
Line 3The complaints go to stderr with fputs, since a stdout that just failed cannot be trusted to carry the message.
Line 4fflush is checked because a buffered write can fail after printf already returned a healthy count, so the flush is the first place the real disk or pipe error can show up.
Important notes
scanf, fscanf and sscanf all follow the same return rule, so feeding a fixed string to sscanf reproduces exactly the number scanf would report for the same characters.
Keep the return value in an int and test r < 0 or r == EOF; EOF is only guaranteed to be negative, and in an unsigned variable it becomes a large positive number so the failure check silently never fires.
Common mistakes
Writing while (scanf("%d", &n) != EOF): a typed letter makes scanf return 0, not EOF, and the letter stays in the stream, so the loop spins forever on the same character.
Using the return as a truth test, as in if (scanf("%d %d", &a, &b)) { ... }: EOF is -1, which is true, so total input failure is taken as success and both uninitialized variables get used.
Treating printf like a status function, as in if (printf(...) != 0) report_error(): a successful printf returns the character count, so the error branch fires on every good call while a real negative return is ignored.
Try it yourself
Change, predict, then run
Call sscanf with the format "%d %d" on the strings "5 7", "5 x", "x 5" and "", after setting both target variables to -1 before each call, and print the return value together with both variables. Note which variables still hold -1 in each case and which return value tells you that.
Open the C workspaceCheck your understanding
A program runs while (scanf("%d", &v) != EOF) { total += v; } and the user types the letter x and presses Enter. What happens?
- scanf returns EOF because the conversion failed, so the loop ends
- scanf skips the offending character and waits for the next number
- scanf returns 0 every time and never consumes the character, so the loop never ends
- The newline satisfies the conversion, so the loop runs one extra time and stops
Show answer
A conversion that meets the wrong kind of character is a matching failure: scanf returns the number of items assigned so far, which is 0 here, and leaves the offending character unread. EOF is reserved for input failure, meaning end of file or a read error before any conversion, so it never appears merely because the text had the wrong shape, which is why the != EOF condition stays true on every repeat.