Learn C Programming

Lesson 4 of 7 · Constants, Qualifiers, Input and Output

Module 3 · Constants, Qualifiers, Input and Output

scanf: Reading Input Without Losing Your Mind

FreeReading

In this lesson

  • Read an integer, a decimal, a character and a word, and print what scanf gave back.
  • Explain the ampersand as the address of the box, and say what happens without it.
  • Skip a leftover newline with a space, and cap a %s with a width.

Bob types 12 34 into his program and it dies without printing anything. He removes one character from a line he did not write, and it works. Then he reads a letter after a number and gets a letter he never typed.

Both are the same lesson, learned twice. scanf uses the format language you already know from printf, mirrored, with one extra rule and one famous trap. This lesson is those two things and nothing else.

scanf is printf's mirror, with one extra rule

printf takes values and writes text. scanf takes text and writes values, which means it needs somewhere to put them.

Handing it a variable would hand it a copy of that variable's value, and writing into a copy changes nothing. So you hand it the address of the box instead, with an ampersand.

One scanf call, and what each part does

int items = scanf("format", &first, &second);

&x           the address of x: where x lives, not what is in it
             the one thing printf never needs and scanf always does

%d   %u      an int, an unsigned int
%ld  %lld    a long, a long long
%lf          a double.  %f here reads a float and ruins a double
%c           exactly one character, whitespace included
%s           one word, stopping at the first whitespace
%19s         the same, writing at most 19 characters plus the marker
%x   %o      a number written in base 16 or base 8

a space      in the format, skip any run of whitespace here
             " %c" is a space then %c, and it is the fix for the trap

returns      how many items were filled in.  EOF when the input ran out
  • Every specifier that fills a variable needs an address, so every one needs an ampersand.
  • Whitespace means spaces, tabs and newlines, and %d, %f and %s skip it before they start.
  • %c is the one that does not skip, which is the whole trap.

So the shape is always the same: a format that mirrors the input, an ampersand on every target, and a returned count you look at.

Print what scanf gave back, every time

scanf returns the number of items it successfully filled in. Not the number of characters, and not whether the values are sensible.

Ignoring that number is the single most common beginner habit that turns a wrong input into a wrong answer instead of a clear message. Lesson 5 is about what to do with it; this lesson just prints it.

#include <stdio.h>

int main(void)
{
    int roll = 0;

    int read = scanf("%d", &roll);

    printf("scanf returned %d\n", read);
    printf("roll is %d\n", roll);
    return 0;
}
scanf returned 1
roll is 47

That output is for the input 47. One item asked for, one item filled in, so the answer is 1.

Type a word instead of a number and the first line reads 0, which is lesson 5's whole subject.

Several values at once, and whitespace in the format

A format can ask for several values. Between them you usually write a space, which means "skip any whitespace here", and that includes the newline you pressed Enter for.

So one format reads values typed on one line or on three, without changing anything.

#include <stdio.h>

int main(void)
{
    int roll = 0;
    char section = '?';
    double cgpa = 0.0;

    int read = scanf("%d %c %lf", &roll, &section, &cgpa);

    printf("scanf returned %d\n", read);
    printf("roll    : %d\n", roll);
    printf("section : %c\n", section);
    printf("cgpa    : %.2f\n", cgpa);
    return 0;
}
scanf returned 3
roll    : 47
section : B
cgpa    : 3.78

That output is for the input 47 B 3.78. It is the same for the same three values typed on three separate lines.

The space before %c is doing real work there, and the next section is why.

%c does not skip whitespace, and a newline is whitespace

Every other specifier skips leading whitespace before it starts reading. %c does not, because sometimes a space is the character you want.

That is reasonable and it causes the most reported bug in every beginner C course. Here is the sequence.

You type 47 and press Enter. %d reads the 4 and the 7, stops at the newline, and leaves it sitting in the stream. Then %c reads the next character, which is that newline.

The input stream as a tape, before and after each read You typed 47 and pressed Enter 4 7 \n the Enter key is a character in the stream after scanf("%d", &roll) 4 7 \n cursor roll is 47, and the newline was never read scanf("%c", &section) reads the newline into section, which is character 10. scanf(" %c", &section) skips whitespace first, so it reads the letter you typed.
Figure 1. The stream after a %d read. The cursor is parked on the newline, and only a space in the format moves it.

The fix is one space in the format string. A space there means "skip any whitespace", so the cursor moves past the newline and reads the character you meant.

This is why the second read in Example 2 above was written " %c" and not "%c".

%s stops at whitespace, and does not know when to stop writing

%s reads one word: it skips leading whitespace, then takes characters until the next whitespace. It cannot read a line with spaces in it, and reading a whole line is Module 10.

The dangerous half is what it does at the other end. A bare %s writes as many characters as arrive, whether or not there is room for them.

The fix is a width: %19s writes at most 19 characters, plus the marker C puts at the end of every piece of text. Nineteen and a marker fit in twenty boxes.

#include <stdio.h>

int main(void)
{
    char word[20] = {0};
    char initial = '?';

    int first = scanf("%19s", word);
    int second = scanf(" %c", &initial);

    printf("first scanf returned  %d, word is %s\n", first, word);
    printf("second scanf returned %d, initial is %c\n", second, initial);
    return 0;
}
first scanf returned  1, word is Progsity
second scanf returned 1, initial is R

That output is for the input Progsity R. Arrays are Module 9 and text is Module 10, so treat char word[20] as twenty boxes in a row for now.

Notice there is no ampersand on word. An array's name already is an address, which is the one exception to the ampersand rule and the reason Module 9 and Module 11 exist.

%lf for a double, always, and only here

In a printf, %f prints both a float and a double, because a float is widened on its way into the call.

In a scanf nothing is widened, because you are handing over an address and the address says nothing about the size. %f means "write four bytes here" and %lf means "write eight".

Use %f for a double and scanf writes four bytes into an eight byte box. The value is wrong, nothing is reported on the Playground, and the number you print is usually zero.

So the rule is short: in a scanf, a double is %lf, every time.

Example 1: two numbers and their sum

The smallest useful read. Two values, one format, and the count printed so that you can see it worked.

#include <stdio.h>

int main(void)
{
    int a = 0;
    int b = 0;

    int read = scanf("%d %d", &a, &b);

    printf("read %d value(s)\n", read);
    printf("%d + %d = %d\n", a, b, a + b);
    return 0;
}
read 2 value(s)
12 + 34 = 46

That output is for the input 12 34. Remove either ampersand and the program compiles on the Playground with nothing said, then misbehaves.

Run in Compiler
Example 2: a character after a number, done correctly

The trap and its fix in one program. The two reads are identical except for one space.

#include <stdio.h>

int main(void)
{
    int roll = 0;
    char section = '?';

    int first = scanf("%d", &roll);
    int second = scanf(" %c", &section);

    printf("returned %d and %d\n", first, second);
    printf("roll %d, section [%c], as a number %d\n", roll, section, section);
    return 0;
}
returned 1 and 1
roll 47, section [B], as a number 66

That output is for 47 on one line and B on the next. Take the space out of the second format and the same input gives section [] over two lines, as a number 10, and the same two returned counts.

Read that last part twice. The return value cannot catch this one, because scanf did exactly what it was asked and succeeded.

Run in Compiler
Example 3: a whole record, the one you would actually write

A name, a roll, a section and a result, read and printed back with the widths of lesson 3.

#include <stdio.h>

int main(void)
{
    char name[20] = {0};
    int roll = 0;
    char section = '?';
    double cgpa = 0.0;

    int read = scanf("%19s %d %c %lf", name, &roll, &section, &cgpa);

    printf("fields read : %d\n", read);
    printf("%-10s%6d%4c%8.2f\n", name, roll, section, cgpa);
    return 0;
}
fields read : 4
Nusrat        47   B    3.78

That output is for the input Nusrat 47 B 3.78. Four specifiers, four values, four counted, and one ampersand missing on purpose because name is an array.

The space before %c is still there. It is doing nothing in this input, because %d stopped on a space rather than a newline, and it costs nothing to leave it.

Run in Compiler

Where this is used

  • Every problem on this platform's judge. Your program is handed a file on its standard input and your reads are how it gets in. The graded problems of this module all start with a scanf line written for you.
  • A configuration parser. Reading a settings file of the shape key value is a loop around a two specifier scanf, and the returned count is how the loop knows to stop. Module 6 closes that loop.
  • The command prompt of any interactive tool. A database shell, a calculator, a game's move entry: all of them read a token, check the count, and act.
  • A bank's overnight import. Millions of fixed shape records are read with one format string per record type. A record whose count comes back short goes to a reject file rather than being guessed at.

Common mistakes

1. Forgetting the ampersand.

int roll = 0;
scanf("%d", roll);

Silent on the Playground, because the check lives behind -Wall. A local gcc -Wall says warning: format '%d' expects argument of type 'int *', but argument 2 has type 'int'. scanf takes the number that is in roll and treats it as an address to write to. What happens next is undefined behaviour: on our run the write was refused and scanf returned 0, and on another machine the program is killed instead.

2. A bare %c after a number.

scanf("%d", &roll);
scanf("%c", &section);

No message anywhere, and both reads return 1. The character read is the newline left by the Enter key, which is character 10. The fix is a space: scanf(" %c", &section).

3. %f for a double.

double cgpa = 0.0;
scanf("%f", &cgpa);

Silent on the Playground, and scanf returns 1 as though it worked. On our run it printed 0.00 for every input. A local gcc -Wall says warning: format '%f' expects argument of type 'float *', but argument 2 has type 'double *' and suggests %lf.

4. A %s with no width.

char word[20] = {0};
scanf("%s", word);

No message at either command line. It works for every short word you test with, and a 40 character word writes 20 characters past the end of the boxes. This is the shape of a whole family of real security holes, and the fix is three characters: %19s.

Brain teaser

Bob's program reads a roll number and a section. He is sure the second line is right because it looks exactly like the first.

#include <stdio.h>

int main(void)
{
    int roll = 0;
    char section = '?';

    int first = scanf("%d", roll);
    int second = scanf(" %c", &section);

    printf("%d %d\n", first, second);
    printf("roll %d, section %c\n", roll, section);
    return 0;
}

The Playground compiles this without a word, because the check that would catch it needs -Wall. Say exactly what value scanf was handed as a destination, and what it then tried to do with that value.

Then the harder half. Bob says the line is fine because roll is 0 and address 0 is the start of memory, so the write should land somewhere harmless. Say what is wrong with that reasoning, and why the answer is not a number at all.

Compare the second line with the first, character by character. Then ask what scanf receives in each case, rather than what you meant to send.

Exercise 1: sum-and-productEasy

Kenji is testing a calculator's two simplest buttons, on numbers big enough that the second answer will not fit where the first one does.

Input. One line with two integers a and b.

Output. Two lines: the sum, then the product.

Constraints. -100000 <= a, b <= 100000. The sum fits in an int and the product does not.

Sample. Input 3 4 gives 7 then 12.

#include <stdio.h>

int main(void)
{
    int a = 0;
    int b = 0;
    scanf("%d %d", &a, &b);

    /* Two printf calls. The product does not fit in an int. */

    return 0;
}

Graded as sum-and-product. Module 2 lesson 2's rule about widening one operand applies here exactly.

Run in Compiler
Exercise 2: greet-nameMedium

Zara's sign-in desk has a name box nineteen letters wide, and one day somebody will type a longer name into it.

Input. At most one line holding one word with no spaces. It may be empty.

Output. Two lines: Hello, <name>! then read and what scanf returned.

Constraints. The word is 1 to 19 characters. Empty input returns -1 and greets an empty name.

Sample. Input Maria gives Hello, Maria! then read 1.

#include <stdio.h>

int main(void)
{
    char name[20] = {0};

    int read = scanf("%19s", name);

    /* Two lines: the greeting, then what scanf gave back. */

    return 0;
}

Graded as greet-name. Leave the 19 in the format exactly as it is, and leave the missing ampersand alone: an array name is already an address.

Run in Compiler
Exercise 3: the student slipMedium

Read a roll number, a section letter and a result, then print them as three aligned lines.

Input. A roll number, a section letter and a decimal, in that order, on one line or several.

Output. Three lines: Roll, Section and CGPA, each label padded to eight characters and each value right aligned in a field of nine.

Sample. Input 47 B 3.78 gives a slip whose three values end in the same column.

#include <stdio.h>

int main(void)
{
    int roll = 0;
    char section = '?';
    double cgpa = 0.0;

    int read = scanf("%d %c %lf", &roll, &section, &cgpa);

    /* Three printf calls, then one more line printing read. */

    return 0;
}

Not graded in this module. Then run it again with the three values on three separate lines and satisfy yourself that nothing changed.

Run in Compiler
Exercise 4: the mean of threeHard

Read three decimal numbers and print their mean to exactly two decimal places.

Input. Three decimal numbers separated by whitespace.

Output. One line with the mean, to two decimal places.

Sample. Input 1 2 4 gives 2.33.

#include <stdio.h>

int main(void)
{
    double a = 0.0;
    double b = 0.0;
    double c = 0.0;
    scanf("%lf %lf %lf", &a, &b, &c);

    /* One printf. Divide by 3.0 and not by 3, and ask yourself why. */

    return 0;
}

Not graded in this module. The hard part is the comment: work out what (a + b + c) / 3 gives when every value is a double, and then what it would give if they were int.

Run in Compiler

Common doubts

  • Why does %s not need an ampersand?

    Because the name of an array already means the address of its first box. It is the one place the rule looks broken, and Module 9 is where it stops looking that way.

  • Can scanf read a whole line with spaces in it?

    Not sensibly. %s stops at the first space, and the tricks that get around it are worse than the tool for the job, which is fgets in Module 10.

  • Should I put a space before every specifier?

    It is harmless everywhere and only necessary before %c. Many people write it everywhere out of habit, and nobody has ever been bitten by doing so.

  • Do I need printf prompts like "Enter a number"?

    Not on a judge, and they will fail your tests, because the judge compares every character you print. Write them while you are experimenting and take them out before you submit.

  • What happens if I type more values than the format asks for?

    The extra ones stay in the stream, waiting for the next read. Nothing is lost and nothing complains, which is exactly what the problems lesson's line-parser depends on.

Key takeaways

  • scanf writes into boxes, so it needs an address, which is what the ampersand gives it.
  • It returns how many items it filled, and printing that number is a habit worth forming.
  • Whitespace in the format means "skip any whitespace here", newlines included.
  • %c is the only specifier that does not skip whitespace, and " %c" is the fix.
  • %s takes one word and needs a width, or it writes past the end of your boxes.
  • In a scanf a double is %lf, and %f there is a silent failure on the Playground.

Next you stop assuming the read worked, and make the returned count do some actual work.

End of lesson 4

Mark it done, and your progress moves with you.

Next: When Input Goes Wrong: Checking What You Read