Formatted Input and Output
Output is straightforward once you know the specifiers. Input is not. scanf is the function most likely to make a beginner's program hang, loop forever, or silently read nothing — and understanding why teaches you more about C than the function itself is worth.
- Format output precisely with width, precision, and flags.
- Match every specifier to its argument type, and explain why a mismatch is undefined behavior.
- Explain what the input buffer is and why
scanfleaves things in it. - Check the return value of
scanfand recover from malformed input. - Write an input loop that cannot be made to hang or spin by any input at all.
1printf in detail
A conversion specification has up to five parts, in this order:
%[flags][width][.precision][length]conversionConversions
| Specifier | Argument type | Prints |
|---|---|---|
%d %i | int | signed decimal |
%u | unsigned int | unsigned decimal |
%x %X | unsigned int | hexadecimal, lower or upper case |
%o | unsigned int | octal |
%f | double | fixed notation, 6 decimals by default |
%e %E | double | scientific notation |
%g %G | double | whichever of %f/%e is shorter |
%c | int (promoted char) | one character |
%s | char * | characters up to the terminating '\0' |
%p | void * | an address |
%% | — | a literal % |
Length modifiers
These adjust the expected argument size and are not optional when the type is not int or double.
| Write | For |
|---|---|
%ld | long |
%lld | long long |
%zu | size_t |
%hd | short |
%Lf | long double |
There is no modifier for float, because a float argument is automatically promoted to double when passed to a variadic function. %f is correct for both.
Width, precision, and flags
printf("[%5d]\n", 42); /* [ 42] width 5, right aligned */
printf("[%-5d]\n", 42); /* [42 ] left aligned */
printf("[%05d]\n", 42); /* [00042] zero padded */
printf("[%+d]\n", 42); /* [+42] always show the sign */
printf("[%8.3f]\n", 3.14159); /* [ 3.142] */
printf("[%.2f]\n", 3.14159); /* [3.14] two decimals */
printf("[%.3s]\n", "abcdef"); /* [abc] first three characters */
printf("[%*d]\n", 6, 42); /* [ 42] width from an argument */For %f precision means digits after the point; for %g it means total significant digits; for %s it means a maximum number of characters. Columns line up when you combine width with left alignment, which is how the type table in week 5 was printed.
A mismatched specifier is undefined behavior, not a formatting quirk. printf("%d", 3.14) makes printf read an int-sized piece of a double's bits. printf("%s", 42) makes it treat 42 as an address and read memory there — usually a crash. printf cannot check: it receives no type information at run time, only the promise your format string made. Week 30 explains the variadic mechanism that makes this unavoidable.
The compiler does check, though, when it can see the format string as a literal. This is one of the most valuable warnings -Wall provides:
warning: format '%d' expects argument of type 'int',
but argument 2 has type 'double' [-Wformat=]2The input buffer
Before scanf makes sense, the buffer does.
When you type at a terminal, your keystrokes do not reach the program one at a time. The operating system collects a whole line and hands it over only when you press Enter. That line — including the newline character — sits in a buffer belonging to stdin. Input functions consume characters from that buffer; anything they do not consume stays there for the next call.
Almost every scanf problem is a consequence of that last sentence.
scanf("%d") stops at the first character that cannot be part of a number — and the newline is one.
3scanf and its three failure modes
scanf takes a format string and the addresses of the variables to fill:
int age;
scanf("%d", &age); /* note the & */The & is the address-of operator from week 13. For now, take it as required: scanf must be able to write into your variable, so it needs to know where the variable lives. Forgetting it is the single most common scanf bug, and it corrupts memory rather than failing cleanly.
It returns a count, and you must check it
scanf returns the number of items it successfully assigned, or EOF if the input ended first.
int age;
if (scanf("%d", &age) != 1) {
fprintf(stderr, "that was not a number\n");
return EXIT_FAILURE;
}A program that ignores this return value has no idea whether its variables were filled. If the user types abc, scanf returns 0, age keeps whatever it had before — possibly garbage, per week 5 — and the program proceeds on nonsense.
Failure mode 1 — the leftover newline
int age;
char initial;
scanf("%d", &age); /* consumes 42, leaves '\n' */
scanf("%c", &initial); /* reads '\n' instantly, no pause */The second prompt appears to be skipped. It was not: it read the newline the first call left behind. %d, %f, and %s all skip leading whitespace, but %c does not — it takes whatever is next, including a newline.
The conventional patch is a space in the format string, which means "skip any whitespace here":
scanf(" %c", &initial); /* the leading space fixes it */Failure mode 2 — the infinite loop
int n;
while (scanf("%d", &n) != 1) {
puts("please enter a number");
}Type abc and this loop runs forever. scanf encounters a, finds it cannot be part of an integer, returns 0 — and leaves abc in the buffer. The next iteration meets the same characters and fails identically, as fast as the processor can manage.
Recovery requires explicitly discarding the bad input:
static void discard_line(void)
{
int c;
while ((c = getchar()) != '\n' && c != EOF) {
/* throw away the rest of the line */
}
}Failure mode 3 — %s and buffer overflow
char name[10];
scanf("%s", name); /* no bound: 50 characters will be written */This is the same class of defect as gets, which week 1 told you never to use. %s writes until it meets whitespace, with no regard for how large your array is. Always give a width, one less than the array size to leave room for the terminator:
char name[10];
scanf("%9s", name); /* at most 9 characters plus '\0' */What professionals actually do. Most production C reads a whole line with fgets and then parses it with strtol or sscanf. That separates "get the input" from "interpret the input", which makes error handling straightforward and leaves nothing unpredictable in the buffer. Week 23 builds exactly that. Learn scanf because you will meet it in every textbook and much existing code — then prefer the alternative.
4Character-at-a-time input and output
int c = getchar(); /* one character, or EOF */
putchar(c); /* one character out */Note the type: getchar returns int, not char. It must be able to return every possible character and the distinct value EOF, which is negative. Storing the result in a char destroys that distinction and can make the end-of-input test fail — on a machine where plain char is unsigned, it can loop forever.
int c; /* correct */
while ((c = getchar()) != EOF) {
putchar(c);
}That loop copies standard input to standard output — the whole of the Unix cat command, and a useful shape to recognize. The extra parentheses around the assignment are required, because = binds looser than != (week 6).
End of input from a terminal is Ctrl-D on Linux and macOS, Ctrl-Z then Enter on Windows.
5Worked example: an input loop that cannot be broken
The goal: read an integer between 1 and 100. Accept correct input, reject everything else with a clear message, and never hang or spin regardless of what is typed — including no input at all.
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
/* Throw away the remainder of the current input line.
Returns false if input ended while doing so. */
static bool discard_line(void)
{
int c;
while ((c = getchar()) != '\n') {
if (c == EOF) {
return false;
}
}
return true;
}
/* Read one integer in [low, high].
Returns false only if input ends; otherwise keeps asking. */
static bool read_int_in_range(const char *prompt, int low, int high, int *out)
{
for (;;) {
printf("%s (%d-%d): ", prompt, low, high);
fflush(stdout);
int value;
int converted = scanf("%d", &value);
if (converted == EOF) {
putchar('\n');
return false; /* Ctrl-D, or piped input ran out */
}
if (converted == 0) {
fprintf(stderr, " not a number — try again\n");
if (!discard_line()) { /* essential: remove the bad text */
return false;
}
continue;
}
if (!discard_line()) { /* remove the trailing newline
and anything after the number */
return false;
}
if (value < low || value > high) {
fprintf(stderr, " %d is out of range — try again\n", value);
continue;
}
*out = value;
return true;
}
}
int main(void)
{
int score;
if (!read_int_in_range("Enter a score", 1, 100, &score)) {
fprintf(stderr, "no input; giving up\n");
return EXIT_FAILURE;
}
printf("\n%-12s %s\n", "score", "bar");
printf("%-12d ", score);
for (int i = 0; i < score / 5; i++) {
putchar('#');
}
printf("\n%-12s %6.1f%%\n", "as percent", (double)score);
return EXIT_SUCCESS;
}gcc -std=c17 -Wall -Wextra -g -o prompt prompt.c
./promptTest it properly
Try each of these. A correct program survives all six.
| Input | Expected |
|---|---|
42 | accepted |
abc | rejected once, prompts again — does not spin |
999 | rejected as out of range |
42abc | accepts 42, discards the rest of the line |
| Enter on its own | prompts again |
| Ctrl-D | exits cleanly with a failure status |
And from a script, which is how real programs are usually driven:
echo "42" | ./prompt ; echo "exit status $?"
echo "abc" | ./prompt ; echo "exit status $?"
printf "" | ./prompt ; echo "exit status $?"The third case — empty input — is the one hand-testing always misses. scanf returns EOF immediately, and a loop that only checks for 0 would never terminate.
Three details worth noting
fflush(stdout) after the prompt. Standard output to a terminal is line-buffered, so a prompt with no trailing newline may not appear before the program blocks waiting for input. Flushing forces it out. Week 26 covers buffering properly.
Errors go to stderr, results to stdout. That separation lets a user redirect the data without losing the diagnostics — ./prompt > results.txt still shows the error messages. Week 31 makes this a design rule.
Three distinct outcomes, three distinct responses. EOF, a conversion failure, and a valid-but-out-of-range value are different situations and the code treats them differently. Collapsing them into one "bad input" branch is how programs end up hanging.
6Common mistakes
| Mistake | What happens | Fix |
|---|---|---|
scanf("%d", age) without & | Memory corruption or a crash | Pass the address. -Wall catches this one. |
Ignoring scanf's return value | Program proceeds on an unassigned variable | Compare it with the number of items you asked for. |
Retrying scanf without discarding input | Infinite loop at full speed | Consume the rest of the line before retrying. |
scanf("%c", &ch) after %d | Reads the leftover newline | scanf(" %c", &ch) — note the space. |
scanf("%s", buf) with no width | Buffer overflow | %9s for a 10-byte array. |
char c = getchar(); | EOF becomes indistinguishable from a character | Declare int c. |
| Prompt not appearing before input | Program looks frozen | fflush(stdout), or end the prompt with \n. |
Checking only for 0, never EOF | Infinite loop on empty or piped input | Handle EOF as its own case. |
7Check yourself
Why does the second prompt seem to be skipped when a %c read follows a %d read?
Because %d stops at the newline and leaves it in the input buffer. %c does not skip whitespace, so it immediately consumes that leftover newline and returns without waiting. Writing " %c" — with a leading space — tells scanf to skip whitespace first.
Why does while (scanf("%d", &n) != 1) puts("try again"); spin forever on letters?
scanf stops at the first character it cannot convert and leaves that character in the buffer. Each iteration sees the same offending text, fails identically, and loops. You must explicitly consume the rest of the line before retrying.
Why must getchar's result be stored in an int?
It returns every possible character value plus a distinct EOF, which is negative. A char cannot represent all of those distinctly. Where plain char is unsigned, EOF becomes a positive value and the end-of-input test never succeeds.
Why can printf not detect that you passed a double where %d was written?
Because it is a variadic function: at run time it receives no type information about the extra arguments, only the format string's claim about them. It reads bytes according to that claim. The compiler can check when the format string is a literal — which is what -Wformat does — but the function itself cannot. Week 30 builds a variadic function and shows why.
Your interactive program works, but breaks when someone pipes input to it. What did you probably forget?
To handle EOF. Interactively the user keeps typing until the program is satisfied; piped input simply runs out, and scanf returns EOF rather than 0. Code that only distinguishes success from conversion failure loops forever once input ends. Always test with printf "" | ./prog.
8Where this leads
You can now get data in and out of a program and defend it against bad input. Weeks 9 and 10 give you the control structures to act on that data — conditionals and loops — which turns these fragments into programs that decide and repeat. The input-validation pattern built here returns in week 23, rewritten with fgets and strtol, and again in week 48, where a fuzzer attacks it.