scanf, Properly This Time

You have called scanf since chapter 1 on a promise that the details would come. This is that lesson: how the format string consumes input, what the whitespace rules really are, why %c plays by different rules than everything else, how widths and skipped fields turn one line of input into values you did not expect, and the fact that separates working programs from lucky ones: scanf reports how much it managed to read, and that report is the truth about your input.

How a Conversion Consumes Input

Each conversion specification reads characters from the input stream and converts them into the variable whose address you supplied. %d skips any leading whitespace, then consumes an optional sign and digits, stopping at the first character that cannot be part of the number. %lf does the same for a double. The stopping rule matters: whatever scanf does not consume, newline included, stays in the stream for the next read to find.

Whitespace in the format string means "skip any amount of whitespace here", which is why "%d %lf" reads two values whether they are separated by one space, seven, or a newline. Between two numeric conversions the skip is automatic anyway, since each skips its own leading whitespace; the writing convention keeps the intent visible.

The %c Exception

%c reads exactly one character, and it is the one specifier that does not skip leading whitespace, because sometimes the character you need is the space or the newline. The classic surprise follows:

Type 19 on one line and A on the next. It works, but only because of one character in the second format string: the space before %c. Without it, %c would read the newline still sitting in the stream after 19, and grade would hold '\n'. The leading space means "skip whitespace first", turning %c back into "read the next visible character". This single space fixes the most-asked scanf question on every student forum since 1989.

The Return Value Is the Point

scanf returns the number of conversions it completed successfully, stopping at the first failure. Read two values and it returns 2 if both succeeded, 1 if the second failed, 0 if the input never matched at all, and EOF if the stream ended before anything was read. On a failed conversion the variable is left untouched, which is why this course initializes every variable: after a failure your program still holds defined values, not indeterminate ones.

Run it three ways: type 42 2.5, then run again and type 42 oops, then just oops. The counts come back 2, 1, and 0, and the untouched variables keep their initial zeros. Acting on the count, retrying, rejecting, explaining, needs if, one chapter away; from then on, every scanf in this course has its return value checked, and the exercise below has you reporting it already.

One more rule, inherited from chapter 1 but now explainable: a failed conversion leaves the offending characters in the stream. A program that loops on scanf("%d") against non-numeric input re-reads the same bad characters forever, the infamous infinite input loop; the fix belongs to the strings chapter, where line-based reading makes input handling robust.

Field Width: Capping a Conversion

A number between the % and the conversion character is a maximum field width: the conversion reads at most that many characters and then stops, whether or not the number it was reading has ended. Exam papers lean on this hard, because it is the one scanf feature whose output is genuinely difficult to predict.

Give it one line, 6789 4321, and the widths do this:

first=2 a=67 b=89
second=1 leftover=4321

%2d took two characters of 6789, making a 67. %4d resumed at the 8, read 89, and stopped at the space, so b is 89. Both conversions succeeded, so the first call returned 2 while quietly reading something no one intended. And the value that never got read did not disappear: 4321 was still in the stream, so the second scanf collected it.

That is the behaviour worth memorizing: an over-narrow width truncates the number, and the remainder feeds the next conversion, shifting every later value along by one. A program that looks like it read the wrong data has usually read the right data with the wrong widths.

Skipping a Field with *

A * between the % and the conversion character means read this and assign nothing. It steps over a field you do not want without making you declare a variable to catch it.

Give it a date whose middle field is of no interest, 12 7 1998:

count=2 day=12 year=1998

Three fields consumed, two variables filled, the middle value discarded. Now look at the count: scanf returned 2, not 3, because the return value counts assignments and a suppressed field is never assigned. So the number to compare the return value against is not the number of conversions in your format string; it is the number of unsuppressed ones.

Literal Characters Must Match

Anything in the format string that is neither a conversion nor whitespace is a literal the input has to supply. That is how a fixed shape gets parsed:

On 123-456 the literal earns its place:

count=2 a=123 b=456

The - in the format matched the - in the input and both numbers arrived. Run the same program on 123 456:

count=1 a=123 b=0

%d read 123, the format then demanded a -, the input offered a space, and the mismatch stopped scanf where it stood. b keeps its initialized zero, and the space and 456 are still in the stream. This is a matching failure, and it is distinct from the input failure you get when the stream ends: both cut the call short, but only one of them means "the data was shaped differently than you promised".

The literal is also doing more work than it appears to. Drop it and write "%d %d" against that same 123-456, and the second conversion reads -456: the hyphen becomes the sign of a negative number rather than a separator. Literals are how you tell scanf which of those two readings you meant.

Scansets: Choosing the Character Set Yourself

%[...] is a conversion whose set of acceptable characters you write out yourself. It reads characters for as long as they belong to the set and stops at the first one that does not. Put ^ first and the set is inverted, so %[^\n] means "read anything that is not a newline" — the standard trick for reading a whole line, spaces included, with scanf.

These conversions store into a character array, which is chapter 7 and 8 material. Take the two declarations on trust for now and watch the conversions:

Input New Delhi 110001, and the brackets in the output make the boundaries visible:

n1=1 word=[New]
n2=1 rest=[ Delhi 110001]

%15s stopped at the first space, as %s always does, so word is just New. The scanset then took the rest of the line — and notice what it begins with: a space. Scansets do not skip leading whitespace. Every numeric conversion does; %c and %[...] are the two that do not.

The Width Is Not Optional Here

%s and %[...] without a width read as much as the input offers and write all of it into your array, past the end when the input is longer. That is a buffer overrun, and the width is the fix. It must be one less than the array size, leaving room for the '\0' the conversion appends: char rest[16] takes %15[^\n]. This is the discipline chapter 8 builds on, and the reason a bare %s never appears in this course.

An Empty Match Is a Failure

A scanset that matches nothing is a matching failure, not an empty string. The trap is the leftover newline: read a number with %d, try %[^\n] next, and the scanset is handed the newline as its very first character, the one character it refuses to match. It assigns nothing, leaves your array untouched, and returns 0. Clearing that newline first is the same problem the " %c" idiom solves, which makes %[^\n] sharper than it looks.

Ranges Are Not Portable C89

The compact form %[a-z] for "any lowercase letter" is what old papers print, and it does work on this platform. C89 does not define it: a - that is neither first nor last in the set is implementation-defined, so a portable program spells the set out as %[0123456789]. Write the range when a question uses it, and know the guarantee comes from your library rather than the language.

Then Prefer fgets Anyway

%[^\n] reads a line, but it fails on an empty line, leaves the newline behind for the next conversion to trip over, and truncates silently at whatever width you chose with no way to distinguish truncation from a short line. fgets, in chapter 8, has none of those problems: it takes the buffer size as an argument, tells you whether it captured the newline, and treats an empty line as an empty line. Learn %[^\n] because exams use it and because it shows exactly what a conversion is; reach for fgets in code you have to trust.

What scanf Is Not For

Robust menus, forgiving input, anything where the user is free to type what they like: none of that is scanf's job, and no amount of format-string cleverness makes it so. Chapter 8 pairs fgets with the string functions to read a line first and interpret it second, which is the shape every dependable C program uses. scanf is the right tool for exactly what exams use it for, and what its format language is genuinely good at: reading a known sequence of values in a known layout.

Key Takeaways

  • Each conversion skips leading whitespace (except %c and %[...]), consumes what matches, and leaves the rest in the stream, trailing newline included.
  • Whitespace in the format string means "skip any whitespace"; " %c" is the idiom for reading the next visible character.
  • scanf returns the number of successful conversions (or EOF); failed conversions leave variables untouched, which is why everything is initialized.
  • A width caps a conversion (%2d): it truncates the value and the remainder feeds the next conversion, shifting every later value along.
  • %*d reads and discards, and a suppressed field is not counted by the return value.
  • A literal in the format string must be matched by the input ("%d-%d"); a mismatch is a matching failure that stops the call where it stands.
  • %[...] reads your own character set and %[^\n] reads a line; neither skips leading whitespace, an empty match fails, and the width must be one less than the array size.
  • Bad input stays in the stream; from chapter 5 onward every return value is acted on, and chapter 8 brings the fully robust line-based approach with fgets.