Reading Input with scanf
Read numbers from standard input with scanf, see why it needs &x (a real address to write into), and meet its return value, the conversion count you will learn to act on in chapter 2.
The Value Comes From Outside
Every program so far has known all of its own answers. int age = 25; puts the 25 in the source, so the program does the same thing on every run. A program that cannot be told anything can only ever do one job.
Lesson 3 ended on a promise. scanf reads a value from outside the program and has to put it somewhere, and that somewhere is why you spent a lesson on addresses. Here it is, whole:
#include <stdio.h>
int main(void)
{
int age = 0;
scanf("%d", &age);
printf("next year you will be %d\n", age + 1);
return 0;
}
Given the input 25 on standard input, the program prints:
next year you will be 26
scanf is short for "scan formatted" and it is the mirror of printf. Its first argument is a format string too, but the specifiers read instead of write: %d in printf means "put an int here", while %d in scanf means "take text from the input that looks like an int, convert it, and store it".
One note on this platform. Programs here receive standard input from a pipe rather than from someone typing at a terminal, so nothing pauses and nothing is echoed back. The input is already there when the program asks for it.
Read the Value, Write the Address
Only one difference between those two calls really matters. printf gets the value because it only reads. scanf gets the address because it has to write.
printf("%d\n", age) is handed a copy of the number 25, and a copy is enough, because printing a variable does not change it.
scanf cannot work that way. Hand it a copy of 25 and it would convert the input perfectly well, then store the result into the copy, which stops existing the moment the call returns. Your variable would never hear about it. To change age, scanf needs to know where age is, and &age is exactly that: the number of the first of age's four bytes. With the address in hand it writes into the same memory the variable occupies, and when the call returns the new value is sitting there.
That is what the & on every scanf argument is doing, and it is why addresses came first.
Leaving Out the &
The next version is wrong. Take the first program and delete one character, the &, so the call reads scanf("%d", age). The mistake is easy to make and the diagnosis is worth reading, because gcc names exactly what is missing:
age.c: In function 'main':
age.c:7:13: warning: format '%d' expects argument of type 'int *', but argument 2 has type 'int' [-Wformat=]
7 | scanf("%d", age);
| ~^ ~~~
| | |
| | int
| int *
int * is spoken "pointer to int" and it is the type of an address of an int. Chapter 3 gives those a lesson of their own; read it here as "an address, not a value". Passing a plain int where an address is expected is undefined behaviour, so nothing whatsoever is promised about what follows. One run did this:
==14==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0xffff8f1eba98 bp 0xfffffc364e00 sp 0xfffffc364720 T0)
==14==The signal is caused by a WRITE memory access.
scanf took the value sitting in age, which was 0, treated that number as an address, and tried to store the input there. Read the second line again: a WRITE memory access. The sanitizer is stating this lesson's point back at you.
The Specifiers Differ by Direction
Here is the trap for anyone who has got comfortable with printf. An int is %d going both ways. A double is not:
| Type | printf |
scanf |
|---|---|---|
int |
%d |
%d |
double |
%f |
%lf |
There is a reason for the asymmetry. printf is handed a value, and a float passed to printf is automatically widened to a double on the way in, so only one size ever arrives and %f covers it. scanf is handed an address and has to write bytes through it. A float and a double are different sizes (4 and 8 bytes on this platform), and an address says nothing about which of them it points at, because an address is only where something starts. The specifier is the one thing telling scanf how many bytes to write, so it has to name the type exactly: %f for a float *, %lf for a double *.
Write %f in a scanf and the compiler tells you:
price.c:7:13: warning: format '%f' expects argument of type 'float *', but argument 2 has type 'double *' [-Wformat=]
and below the usual caret it prints %lf, the compiler naming the fix for you.
Two Values in One Call
A format string can ask for more than one conversion. Give scanf one address per conversion, in the same order:
#include <stdio.h>
int main(void)
{
int servings = 0;
double grams = 0.0;
scanf("%d %lf", &servings, &grams);
printf("%d servings of %.1f grams is %.1f grams total\n", servings, grams, servings * grams);
return 0;
}
Given the input 4 62.5, it prints:
4 servings of 62.5 grams is 250.0 grams total
The space between %d and %lf means "skip any whitespace here", and any amount counts, including none. Spaces, tabs, and newlines are all whitespace, so the two values can arrive on one line or on two and this call cannot tell the difference: feeding the same program 4 and 62.5 on separate lines prints the identical output.
scanf Reports How Much It Read
scanf returns an int, and that number is the count of conversions it actually completed. It is a value like any other, so you can store it and print it:
#include <stdio.h>
int main(void)
{
int number = -1;
int converted = scanf("%d", &number);
printf("scanf converted %d value(s)\n", converted);
printf("number is %d\n", number);
return 0;
}
Given the input 25:
scanf converted 1 value(s)
number is 25
Given the input hello:
scanf converted 0 value(s)
number is -1
Nothing in that input looked like an int, so scanf converted nothing, returned 0, and left number alone. It did not zero it and it did not store a partial result. It never touched those four bytes at all, which is why they still hold the -1 written there at the declaration.
That -1 is doing real work. Had the variable been written int number; with no initializer, the failed conversion would have left the bytes indeterminate, and printing them would have been lesson 2's undefined behaviour rather than a demonstration of anything. A variable you are about to read into is precisely a variable you cannot assume got written, so the rule about initializing at the point of declaration is what makes this program's output mean something.
And there is the bug in miniature. Both runs print a number that looks like an answer, and only one of them is. A program that reads input without looking at the count carries on computing with whatever the variable happened to hold, which is how a great deal of real software has broken. scanf told you. Acting on what it told you means sending the program down a different path when the count is not what you expected, and that is the if statement, which opens chapter 2. Until then, print the count and watch it move: it is the difference between a program that read your input and a program that only thinks it did.
Key Takeaways
scanfreads text from standard input, converts it according to its format string, and stores the result in your variable.printftakes the value of a variable because it only reads it.scanftakes the address, with&, because it must write into the variable's bytes. That is the whole reason lesson 3 came first.- Omitting the
&passes a value where an address is expected. It is undefined behaviour, and-Wallreports it asexpects argument of type 'int *'. intis%din both directions, but adoubleis%fto print and%lfto read.scanfneeds the exact type because the specifier is what decides how many bytes it writes through the address.- One call can perform several conversions, taking one address per conversion in order. Whitespace in the format string skips any run of spaces, tabs, or newlines, so input can be spread across lines freely.
scanfreturns the number of conversions it completed. On input it cannot convert, it returns a smaller count and leaves those variables untouched, so the initializer you gave a variable is what stands between you and reading indeterminate bytes.- Real code must act on that return value. Chapter 2's
ifstatement is the tool; for now, print the count and see it change.
Create an account to track your progress and access interactive exercises. Already have one? Sign in.
Reading Input with scanf - Quiz
Test your understanding of the lesson.
Practice Exercises
Read Two Numbers and Count the Conversions
Read an int and a double from standard input with a single scanf call, then print each value back and print how many conversions scanf reported. Declare quantity as an int initialized to -1 and price as a double initialized to -1.0, because one test feeds in text that is not a number: scanf will convert nothing and leave both variables exactly as you initialized them, and those are the values your program must print. Remember that a double is read with %lf even though it is printed with %f, and that every variable scanf writes into is passed as an address with &.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!