Why this matters
So far every program has computed with values typed into the source. Real programs read their data: from the keyboard, from files, from other programs through pipes. In C the basic tool for reading formatted input is scanf, and it comes with a responsibility that beginners often skip: input can be wrong, and scanf tells you so only through its return value. A program that ignores that value will happily compute with garbage. This lesson teaches scanf together with the habit of checking it.
Standard input
Every process starts with three open streams:
+-----------+
stdin --> | program | --> stdout (normal output: printf)
| | --> stderr (error messages)
+-----------+
- stdin (standard input) is where input comes from: the keyboard when you run a program in a terminal, a file with
./prog < data.txt, or another program withecho 3 4 | ./prog. The exercise checker feeds each test case's input to stdin. - stdout is where
printfwrites. - stderr is a second output stream meant for error messages. It normally goes to the terminal too, but stays separate when stdout is redirected to a file or pipe, so errors are not mixed into data. Write to it with
fprintf(stderr, "...").
Input arrives as a sequence of characters. Typing 42 and Enter sends the three characters '4', '2', '\n'. Turning characters into numbers is exactly the digit arithmetic from the characters lesson, and scanf does it for you.
scanf basics
scanf ("scan formatted") is the mirror image of printf: a format string describes what to read, and each conversion stores a value into a variable:
#include <stdio.h>
int main(void)
{
int a = 0, b = 0;
int n = scanf("%d %d", &a, &b);
printf("scanf returned %d, a = %d, b = %d\n", n, a, b);
return 0;
}
$ echo "3 4" | ./prog
scanf returned 2, a = 3, b = 4
Why the &?
printf("%d", a) needs the value of a. scanf needs to store into a, and a C function cannot change its caller's variables when given only their values (you will see why in the functions module). So you pass the address of the variable: &a means "where a lives in memory". scanf writes the converted number to that address. This is your first contact with pointers, which get a whole module; for now, remember: scanf needs & before each plain variable.
Conversion specifiers
| specifier | reads into | note |
|---|---|---|
%d | int | optional sign, decimal digits |
%ld, %lld | long, long long | |
%u | unsigned int | |
%f | float | not double! |
%lf | double | the one you usually want |
%c | char | reads one character, does not skip whitespace |
%s | a word into a char array | dangerous without a width; covered with strings |
Note the asymmetry with printf: printf prints a double with %f, but scanf reads a double with %lf. gcc catches the mismatch:
warning: format '%f' expects argument of type 'float *', but argument 2 has type 'double *' [-Wformat=]
Whitespace
Numeric conversions (%d, %lf, ...) skip any leading whitespace: spaces, tabs and newlines. So "3 4", "3\n4" and " 3 4 " all read the same way. A space in the format string also matches any amount of whitespace, including none. %c is the exception: it reads the very next character, even a space or newline. To skip whitespace before a character, put a space in the format: scanf(" %c", &ch).
The return value: always check it
scanf returns:
- the number of items successfully converted and stored, from 0 up to the number of conversions in the format, or
EOF(a macro from<stdio.h>, -1 on all common systems) if input ended before the first conversion.
When a conversion fails, scanf stops right there and leaves the offending characters unread in the input. Running the program above with various inputs:
input "3 4" -> scanf returned 2, a = 3, b = 4
input "3 abc" -> scanf returned 1, a = 3, b = 0
input "abc" -> scanf returned 0, a = 0, b = 0
input "" -> scanf returned -1, a = 0, b = 0
input "3.7 2" -> scanf returned 1, a = 3, b = 0
input "12x" -> scanf returned 1, a = 12, b = 0
The "3.7 2" case is instructive: %d reads 3 and stops at ., which is not part of an integer. The second %d then sees .7, fails, and b is never written. Because we initialized b to 0 it prints 0; had we not, it would print garbage.
Acting on the result: a first look at if
To react to bad input you need to make a decision. The control-flow module covers if in detail; here is all you need for now:
if (condition) {
/* statements run only when the condition is true (nonzero) */
}
Putting it together, a program that validates its input:
#include <stdio.h>
int main(void)
{
double price = 0.0;
int quantity = 0;
if (scanf("%lf %d", &price, &quantity) != 2) {
fprintf(stderr, "error: expected a price and a quantity\n");
return 1;
}
if (quantity < 0) {
fprintf(stderr, "error: quantity cannot be negative\n");
return 1;
}
printf("total: %.2f\n", price * quantity);
return 0;
}
$ echo "2.50 4" | ./prog
total: 10.00
$ echo "2.50 four" | ./prog
error: expected a price and a quantity
$ echo $?
1
Notice the pattern, which you will use for the rest of the track:
- Call the input function and compare its result with what success looks like (
!= 2). - On failure, explain the problem on stderr and exit with a nonzero status.
- Validate the values too (ranges, zero divisors), not just that they were numbers.
- Only then compute.
Limits of scanf
scanf is fine for learning and for well-formed input, but it has weaknesses: after a failed conversion the bad characters stay in the input, so retrying in a loop can spin forever on the same garbage; %d on a number too large for an int is undefined behavior; and "12x" is accepted as 12. Robust programs usually read a whole line with fgets and then parse it with strtol or strtod, which report exactly where parsing stopped. You will learn that approach in the input/output module.
Common mistakes
- Forgetting the
&:scanf("%d", x);passes the value of x where an address is expected. gcc warns, and the program usually crashes with a segmentation fault:warning: format '%d' expects argument of type 'int *', but argument 2 has type 'int' [-Wformat=] %ffor a double. Use%lfin scanf.- Not checking the return value, then computing with uninitialized or stale variables.
%cpicking up a leftover newline. Afterscanf("%d", &n)on input42\nZ, a followingscanf("%c", &ch)reads'\n', not'Z'. Use" %c".- Putting
\nat the end of the format (scanf("%d\n", &n)). In scanf, whitespace in the format means "skip all whitespace", so it keeps reading until it sees a non-space character, and an interactive program appears to hang after you press Enter. - Error messages on stdout. They end up mixed into the program's real output. Use stderr.
Recap
- Programs read from stdin, write results to stdout and errors to stderr (
fprintf(stderr, ...)). scanf(format, &var, ...)converts input text into values; pass the address of each variable with&.%dint,%lfdouble,%cchar (" %c"to skip whitespace); numeric conversions skip leading whitespace.scanfreturns the number of items stored, orEOFat end of input. Compare it with the expected count every time.- On bad input: message to stderr, nonzero exit status. Validate values as well as format.
Going further: run a scanf program in the sandbox terminal and type input by hand. Press Ctrl-D at the start of a line to signal end of file and watch scanf return -1. Then try ./prog < file.txt with a file of numbers.