Menu

scanf in C: Reading Input, the & Rule, and Why It Stops Working

How to read input with scanf in C - why the & is required, how whitespace is handled, the %s buffer overflow and its width fix, checking the return value, the leftover-newline bug, and fgets as the robust alternative.

This page includes runnable editors - edit, run, and see output instantly.

scanf is printf's mirror: same header, similar-looking format strings, opposite direction. It reads text from standard input, converts it according to the format, and stores the results through the pointers you give it.

It is also the function that produces more confused beginners than any other in C, because three of its behaviors are surprising and one of them is a security hole. This page covers all four.

The Basic Shape, and Why &

Type a number and press Enter to run this. The &age is not decoration. C passes every argument by value, so if you wrote scanf("%d", age) the function would receive a copy of age's current (garbage) value and have no way to reach the variable itself. &age passes the variable's address, which scanf then writes through. See pointers for the full picture.

Omitting the & is the single most common scanf mistake. The compiler with -Wall catches it (format '%d' expects argument of type 'int *'); without warnings enabled it compiles and corrupts memory at some random address.

Reading Several Values

One call can read several values. Whitespace in the input - spaces, tabs, newlines - separates them, and the numeric specifiers skip any amount of it.

The literal spaces in "%d %d %d" are actually redundant - %d already skips leading whitespace - but they make the format readable. What is not redundant is checking the return value, covered below.

Non-whitespace characters in the format must match the input exactly, which is how you read structured input:

The : in the format demands a : in the input. Type 9:30 and it parses; type 9 30 and it fails after reading the 9.

Always Check the Return Value

scanf returns the number of items successfully assigned - not the number of characters, and not a success flag. It returns EOF if input ended before anything was converted.

Type hello at the prompt. scanf returns 0, n is left untouched - still holding whatever garbage it started with - and the offending text is still sitting in the input stream. That last part is what turns a single bad input into an infinite loop:

/* BUG: spins forever on non-numeric input */
while (scanf("%d", &n) != 1) {
    printf("Try again: ");
}

scanf never consumes the text it could not convert, so the next call fails on the same characters, forever. To recover you must discard the rest of the line yourself:

The EOF check matters: when the input stream ends (the user presses Ctrl+D, or a piped file runs out), scanf returns EOF forever after, so a loop that only tests != 1 retries for eternity. Handle "bad input" and "no more input" as the two different cases they are. With that in place, the discard_line helper is worth keeping around in any program that uses scanf on human input.

The %s Buffer Overflow

%s reads characters into a char array. Note there is no & - an array name already decays to a pointer to its first element, so &name would be the wrong type.

The problem is that plain %s has no idea how big your array is:

char name[10];
scanf("%s", name);      /* DANGEROUS: types 40 characters, writes 41 bytes */

Nothing stops the write at the array's end. It runs on into whatever is next in memory - other variables, the return address - which is the classic buffer overflow, and historically the most exploited bug class in C. The fix is a width between the % and the s, one less than the array size to leave room for the terminating '\0':

The width has to be written as a literal in the format string, which makes it annoying to keep in sync with the array size - one more reason most production C uses fgets instead.

The second %s surprise: it stops at the first whitespace. Type Ada Lovelace and name holds Ada; Lovelace waits in the stream for the next read. %s reads a word, not a line.

The Leftover-Newline Bug

This is the "scanf not working" everyone hits. Numeric specifiers skip leading whitespace; %c does not.

Here is what happens without that space. You type 30 and press Enter. %d consumes the 3 and the 0 and stops at the newline, leaving '\n' in the buffer. The next scanf("%c", &initial) reads that newline as the character and returns instantly - the prompt appears and is skipped in the same breath.

A space in the scanf format string means "skip any amount of whitespace here", so " %c" steps over the stray newline and waits for a real character. The space is only needed before %c and %[; every other specifier skips whitespace on its own.

Reading Floats and Chars

The specifiers are mostly shared with printf, with one important difference: scanf needs to know the exact size of the destination, because it is writing through a pointer.

Try 1.5 2.25 7 x.

%f reads a float, %lf reads a double. In printf both print the same way because floats are promoted to double before the call - but in scanf there is no promotion, and using %f with a double * writes four bytes into an eight-byte variable, leaving it corrupt. The %c here needs no leading space because the %d before it already stopped at a space, not a newline. (Full table on format specifiers.)

fgets: the Robust Alternative

For anything a person types, read the whole line and parse it afterwards. fgets takes the buffer size as an argument, so it physically cannot overflow, and when the line fits it consumes the newline too, so nothing is left behind to confuse the next read. (A line longer than the buffer leaves its tail unread - the missing '\n' in what you got is how you detect that.)

Three points about that pattern:

  • sizeof line passes the real size, so changing the array size needs no other edit - unlike the %19s literal.
  • fgets keeps the newline if the line fit, which is why the strcspn trim is there. That is also how you detect a line too long for the buffer: no '\n' in what you got.
  • Parse afterwards. strtol for integers (it can also tell you where parsing stopped), strtod for floating point, or sscanf(line, "%d %d", &a, &b) to use a scanf format against a string you already hold safely.

sscanf deserves a mention of its own: it is scanf reading from a string instead of the input stream, and it combines perfectly with fgets. You get scanf's convenient parsing with none of its stream problems.

Common Mistakes

  • Forgetting & on a non-array argument. Compile with -Wall.
  • %s with no width. A buffer overflow waiting for a long input.
  • Ignoring the return value. Then reading a variable scanf never assigned.
  • Looping on a failed scanf without discarding input. An infinite loop.
  • %c without a leading space after reading a number. The skipped-prompt bug.
  • Using %f for a double. Silent corruption; scanf needs %lf.
  • Expecting %s to read a line. It reads one whitespace-delimited word.

Frequently Asked Questions

Why does scanf need an & in C?

Because C passes arguments by value. scanf must change your variable, so it needs the variable's address, not a copy of its value: scanf("%d", &age). The one exception is a character array, whose name already is an address - scanf("%19s", name) takes no &.

Why does scanf skip my input for a char or a string?

A previous scanf("%d", ...) left the Enter key's newline sitting in the input buffer. %d and %f skip leading whitespace, but %c does not, so it reads that newline instead of waiting. The fix is a space in the format - scanf(" %c", &ch) - which tells scanf to skip whitespace first.

How do I read a string with scanf safely in C?

Give %s a maximum width one smaller than your buffer: char name[20]; scanf("%19s", name);. Without the width, %s writes as many characters as the user types and happily runs past the end of the array. Also remember %s stops at the first whitespace, so it cannot read a full name with a space in it.

Should I use scanf or fgets in C?

Use fgets for anything a human types. It reads a whole line into a sized buffer, cannot overflow, and (when the line fits) consumes the newline too, so the classic mixed-input bugs disappear. Parse the line afterwards with sscanf or strtol. scanf is fine for small exercises and well-formed machine input.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED