Computer ScienceCore18 min read

Input and Output

printf and scanf, and the ampersand everybody forgets

This topic appears in:

01

printf: formatted output

printf takes a format string and then one value for each placeholder in it. The placeholders begin with % and tell printf what kind of value to expect and how to display it.

The format string is printed literally except for the placeholders, so ordinary text, spaces and punctuation can be mixed in freely. Escape sequences beginning with a backslash produce characters that cannot be typed directly.

PlaceholderForExample output
%dint42
%ffloat or double3.140000
%.2ffloat, 2 decimal places3.14
%ca single characterA
%sa stringAyesha
%5dint, right-aligned in 5 columns 42
\nnewlinemoves to the next line
\ttabmoves to the next tab stop
printf("Name: %s, Marks: %d, Average: %.2f\n", name, marks, avg);the values are supplied in the same order as the placeholders, and there must be exactly as many
02

scanf, and the ampersand

scanf reads input and stores it in variables. It takes a format string, exactly like printf, and then the addresses of the variables to fill — which is what the & is for.

The reason is that C passes arguments by value: a function receives a copy and cannot change the original. To let scanf modify your variable, you give it the variable's address rather than its value, and scanf writes through that address.

int marks;scanf("%d", &marks);/* & gives the ADDRESS of marks */char name[20];scanf("%s", name);/* no & — an array name is already an address */the one exception: arrays, including strings, need no ampersand

Forgetting the & is the most common C error there is

scanf("%d", marks); passes the value of an uninitialised variable and asks scanf to treat it as an address. The program may crash, may silently corrupt memory, or may appear to work. Modern compilers warn about it; older ones do not. If a program using scanf behaves inexplicably, check the ampersands first.

03

Reading text

scanf("%s", name) reads a single word and stops at the first space, so "Ayesha Khan" leaves "Khan" unread and the next scanf picks it up unexpectedly. To read a whole line including spaces, use fgets(name, 20, stdin), which also limits how many characters it will accept.

That limit matters. scanf("%s", name) into a 20-character array will happily write 50 characters if the user types 50, overwriting whatever memory follows. This is a buffer overflow, and it is both a common bug and a classic security vulnerability.

Worked example

Write a program that reads a student's name and two marks, then prints the name and the average to two decimal places.

  1. char name[30]; int m1, m2; float avg;Declare everything first, with the array large enough for a realistic name.
  2. printf("Enter name: "); scanf("%s", name);A prompt before every input, and no & because name is an array.
  3. printf("Enter two marks: "); scanf("%d %d", &m1, &m2);Two placeholders, two addresses, in the same order. The space in the format string lets scanf skip whitespace between the numbers.
  4. avg = (m1 + m2) / 2.0;The 2.0 forces floating-point division. With 2 the answer would be truncated.
  5. printf("%s scored %.2f\n", name, avg);%.2f gives exactly two decimal places, which is what the question asked for.

Declare, prompt, scanf with & on the numbers, divide by 2.0, print with %.2f.

04

Getting the placeholders right

The format string and the values must agree. C does not check this: printf("%d", 3.7) compiles and prints nonsense, because printf believes it is looking at an integer and interprets the bits accordingly.

Three rules prevent almost every problem. Use one placeholder per value and no more. Match the type exactly — %d for int, %f for float, %lf when reading a double with scanf. And remember that %f prints six decimal places by default, so a money value needs %.2f.

Before you leave this chapter

  1. printf takes a format string plus one value per placeholder, in order.
  2. %d int, %f float, %c char, %s string; %.2f fixes the decimal places.
  3. scanf needs the ADDRESS of each variable, so use & — except for arrays and strings.
  4. scanf("%s") stops at the first space; fgets reads a whole line and limits the length.
  5. Mismatched placeholders compile silently and print nonsense.
05

Building readable output

Output that a person has to decipher wastes the work that produced it. Two devices make the difference, and both are examinable.

Field widths align columns: %10s prints a string right-aligned in ten characters, and %-10s left-aligns it. Combined with \t for tabs, a table of results lines up rather than staggering across the screen.

Labels make a number mean something. printf("%d", t) prints a bare number; printf("Total marks: %d out of %d\n", t, max) tells the reader what it is. In an exam, unlabelled output loses marks even when the calculation is correct.

Worked example

Print a table of three students with names left-aligned in 12 columns and marks right-aligned in 5.

  1. Header: printf("%-12s%5s\n", "Name", "Mark");The same widths are used for the header as for the data, so the columns align.
  2. Each row: printf("%-12s%5d\n", name, mark);The minus sign left-aligns the name; the number right-aligns, which is how figures should be read.
  3. A name longer than 12 characters overflows the column rather than being cut.The width is a minimum, not a maximum — worth knowing before choosing it.
  4. Choose the width from the longest realistic value, not the longest one in your test data.A table that aligns for three test names and breaks for a real class is not finished.

Use %-12s for the name and %5d for the mark, with the same widths in the header row.

Practice questions

6 questions · 20 marks · full working on every one

Try each one on paper first, then open the working. The marks are shown where they are actually awarded, because that is where they are actually lost.

Short questions

3 · 6 marks

Two marks each, in the style of the short-question section of the paper. Answer in two or three lines.

SQ1[2 marks]
Why does scanf require an ampersand before a variable name?
Model answer

C passes arguments by value, so a function receives a copy and cannot alter the caller's variable. The & supplies the variable's address instead, and scanf writes the input through that address into the original variable.

Examiner tip. The phrase "passed by value" is the reason, and it also explains the exception: an array name already evaluates to an address.

SQ2[2 marks]
Why is no ampersand used when reading a string with scanf?
Model answer

An array name in C already evaluates to the address of its first element, so name is already what scanf needs. Writing &name would supply the address of the array itself, which is a different type.

Examiner tip. This is the one exception to the & rule, and the exam asks about it because it looks inconsistent until you know why.

SQ3[2 marks]
What does %.2f do, and why is it needed?
Model answer

It prints a floating-point value to exactly two decimal places. Plain %f prints six by default, so a price would appear as 249.990000 instead of 249.99.

Examiner tip. Give the contrast with plain %f. It shows you know what the default is rather than just reciting the syntax.

Solved numericals

2 · 8 marks

Full working, one step per line, with the marks shown where they are awarded.

N1[4 marks]
Write a C program that reads the length and width of a rectangle as floats and prints the area to one decimal place.
Full working
  1. #include <stdio.h> and int main() {[1]
  2. float length, width, area; with prompts before each input[1]
  3. scanf("%f", &length); and the same for width — ampersands requiredmissing & loses this mark[1]
  4. area = length * width; printf("Area = %.1f\n", area);%.1f for one decimal place[1]

Declare floats, prompt and scanf with &, multiply, print with %.1f.

Examiner tip. Reading a float uses %f in scanf but a double needs %lf. Mixing them is a silent fault that produces nonsense values.

N2[4 marks]
Identify three faults in this fragment: int age; printf("Age: "); scanf("%f", age); printf("You are %s\n", age);
Full working
  1. Fault 1: %f is used to read into an int — it should be %dtype mismatch in scanf[1]
  2. Fault 2: the ampersand is missing — it should be &agescanf needs the address[1]
  3. Fault 3: %s is used to print an int — it should be %dprintf would treat the number as a memory address and read from it[1]
  4. Corrected: scanf("%d", &age); and printf("You are %d\n", age);[1]

Wrong scanf placeholder, missing &, and wrong printf placeholder.

Examiner tip. The %s fault is the most dangerous: printf treats the integer as an address and tries to read a string from it, which typically crashes.

Long questions

1 · 6 marks

Theory and numerical together, as they appear in the long-question section.

LQ1[6 marks]
A program must read a student's full name and three subject marks, then display a formatted result line.
  1. Explain why scanf("%s", name) is unsuitable for a full name, and state what to use instead.
  2. Write the input section of the program.
  3. Explain what a buffer overflow is in this context and how it is avoided.
Mark scheme
  1. scanf("%s", …) stops reading at the first whitespace, so only the first word of the name is stored[1]
  2. The remainder stays in the input buffer and is picked up by the next scanf, corrupting the marksthe knock-on effect is the point[1]
  3. Use fgets(name, 30, stdin), which reads the whole line including spaces[1]
  4. char name[30]; int m1, m2, m3; then the fgets call and scanf("%d %d %d", &m1, &m2, &m3);three placeholders, three addresses[1]
  5. A buffer overflow occurs when more characters are written than the array can hold, overwriting adjacent memory[1]
  6. It is avoided by using a function that takes a length limit — fgets does, plain scanf("%s") does notthe limit is the defence[1]

(a) it stops at a space and leaves the rest to corrupt the next input; use fgets (b) fgets plus scanf with three ampersands (c) writing past the end of the array; fgets limits the length

Examiner tip. The knock-on effect in part (a) is worth spelling out: the leftover "Khan" is read by the next scanf as though it were a mark, so the failure appears somewhere else entirely.