Operators, expressions and input · about 50 min

Reading input with scanf and checking it

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)
            +-----------+

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

specifierreads intonote
%dintoptional sign, decimal digits
%ld, %lldlong, long long
%uunsigned int
%ffloatnot double!
%lfdoublethe one you usually want
%ccharreads one character, does not skip whitespace
%sa word into a char arraydangerous 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:

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:

  1. Call the input function and compare its result with what success looks like (!= 2).
  2. On failure, explain the problem on stderr and exit with a nonzero status.
  3. Validate the values too (ranges, zero divisors), not just that they were numbers.
  4. 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

Recap

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.

Exercises

Exercise 1 · write code

Add two numbers

Read two integers from standard input and print their sum, exactly in this form:

sum: 7

(for the input 3 4). The numbers may be separated by any amount of whitespace, including newlines. For this exercise you may assume the input is valid.

File: main.c

Starting point

#include <stdio.h>

int main(void)
{
    int a = 0;
    int b = 0;
    /* read a and b with scanf */
    printf("sum: %d\n", a + b);
    return 0;
}

Example

input

3 4

expected output

sum: 7

Example

input

-10
  25

expected output

sum: 15
Hint 1

scanf("%d", &a); reads one integer into a. Note the &.

Hint 2

One call can read both: scanf("%d %d", &a, &b);

Reference solution
#include <stdio.h>

int main(void)
{
    int a = 0;
    int b = 0;
    scanf("%d %d", &a, &b);
    printf("sum: %d\n", a + b);
    return 0;
}

Exercise 2 · write code

Temperature conversion

Read a temperature in degrees Celsius (a decimal number such as 21.5) and print it in Fahrenheit with one decimal, using F = C * 9 / 5 + 32, exactly:

21.5 C = 70.7 F

Print the Celsius value with one decimal as well. Read into a double (watch the scanf specifier).

File: main.c

Starting point

#include <stdio.h>

int main(void)
{
    double c = 0.0;
    scanf("%f", &c);
    printf("%.1f C = %.1f F\n", c, c * 9 / 5 + 32);
    return 0;
}

Example

input

21.5

expected output

21.5 C = 70.7 F

Example

input

-40

expected output

-40.0 C = -40.0 F
Hint 1

Read the compiler warning about the scanf format.

Hint 2

printf uses %f for double, but scanf needs %lf for double (%f means float in scanf).

Reference solution
#include <stdio.h>

int main(void)
{
    double c = 0.0;
    scanf("%lf", &c);
    printf("%.1f C = %.1f F\n", c, c * 9 / 5 + 32);
    return 0;
}

Exercise 3 · quiz

What scanf returns

int n = scanf("%d %d", &a, &b); runs three times with three different inputs. What is n in each case?

  1. input 7 x
  2. input hello
  3. empty input (end of file right away)
Answer
  • 1, 0, -1

scanf returns the number of items it successfully converted and stored. With "7 x" it stores 7 into a and then fails on x: 1. With "hello" the very first conversion fails: 0. If the input ends before the first conversion, it returns EOF, which is -1 on every common system. Note that 0 and EOF mean different things: "garbage" versus "nothing left".

Exercise 4 · write code

Division with validation

Read two integers a and b and print the integer quotient and remainder of a / b:

17 / 5 = 3 remainder 2

But validate first:

Print to standard error with fprintf(stderr, "...\n");. Use if as shown in the lesson.

File: main.c

Starting point

#include <stdio.h>

int main(void)
{
    int a = 0;
    int b = 0;
    scanf("%d %d", &a, &b);
    printf("%d / %d = %d remainder %d\n", a, b, a / b, a % b);
    return 0;
}

Example

input

17 5

expected output

17 / 5 = 3 remainder 2

Example

input

-17 5

expected output

-17 / 5 = -3 remainder -2
Hint 1

Compare scanf's return value with 2: if (scanf("%d %d", &a, &b) != 2) { ... }.

Hint 2

Inside the braces: fprintf(stderr, "error: expected two integers\n"); and then return 1;.

Hint 3

Check b == 0 the same way, before dividing, and return 2.

Reference solution
#include <stdio.h>

int main(void)
{
    int a = 0;
    int b = 0;
    if (scanf("%d %d", &a, &b) != 2) {
        fprintf(stderr, "error: expected two integers\n");
        return 1;
    }
    if (b == 0) {
        fprintf(stderr, "error: division by zero\n");
        return 2;
    }
    printf("%d / %d = %d remainder %d\n", a, b, a / b, a % b);
    return 0;
}