LW Leon Learn

Writing C on paper

Write C with a pen: no compiler, no autocomplete, no running. Five short lessons: run a program in your head, draw memory, memorise the skeletons, catch the slips a compiler would catch, and mark your own work.

What you need first

  • You have compiled and run one small C program before. If not, read the C first-steps guide first. This guide starts at the point where no computer is there to help.
  • A pen or pencil, an eraser, and squared or lined paper. Tables are easier on squared paper.
  • About 20 minutes for each lesson. Do the check questions on paper, not in your head.
01

Read like a computer

Goal You can run a short C program by hand, one line at a time, and write down every value it uses.

When you write code on paper, nothing tells you that a line is wrong. Your only check is to run the program yourself, half in your head and half on the page. This is called a dry run: you act as the computer, with a pen, one line at a time.

A computer has no idea what your program is for. It does exactly what each line says, in order, and it remembers only what is stored in variables. A variable is a named place that holds a value. While you trace, do the same. Do what the line says, not what you meant it to say.

The tool is a trace table. A trace table has one column for each variable and one row for each line that runs. Build it in four steps.

1. Number the lines of the program in the margin, starting at 1.
2. Draw the table. The first column is Line. Then one column for each variable. The last column is Note, where you write what happened, for example "test is true", or what the program printed.
3. Run the program. Each time a line runs, add a row. Write the line number, then the value of every variable after that line has finished. Copy unchanged values down from the row above, so that every row can be read on its own.
4. Use two marks for a variable with no value. A dash (–) means the variable does not exist yet. A question mark (?) means it exists but nothing has been stored in it. A correct C program never reads a variable that still holds a question mark.

A loop line runs many times, so it gets many rows. For a for loop, add one row each time the computer reaches the test. Write the values after the update part (such as i++), and write the result of the test in Note. The last row of a loop is the visit where the test is false. The body does not run after it. A variable declared inside the for line, such as i, stops existing when the loop ends, so write a dash for it afterwards.

An if line gets one row. If its test is false, the next row is the first line after the closing brace of the if.

If the program calls a function, draw a second small table for that call, with columns for its parameters and its own variables. Number the calls (f#1, f#2). When the function returns, write the returned value in the Note column of the line that made the call.

New words

dry run
Running a program by hand, with a pen, without a computer.
trace table
A grid with one column per variable and one row per line that runs.
variable
A named place that holds one value, such as int total.
test
The yes-or-no question in an if or a for, such as i <= 4. Its answer is true or false.
remainder (%)
What is left after division. 7 % 2 is 1, and 6 % 2 is 0. A number is even when n % 2 is 0.

Worked example

C11
#include <stdio.h>

int main(void) {
    int total = 0;
    for (int i = 1; i <= 4; i++) {
        if (i % 2 == 0) {
            total = total + i;
        }
    }
    printf("%d\n", total);
    return 0;
}
Predict the output first, then show it

Expected output

6

Step by step

Set up first. Number the lines 1 to 12: the #include line is 1, int main(void) { is 3, and the last } is 12. The page does not print numbers, so count them yourself. Draw four columns: LineitotalNote. The computer starts at the first line inside main, which is line 4.
Line 4i = –total = 0int total = 0; creates total and stores 0. The variable i does not exist yet, so it gets a dash.
Line 5i = 1total = 0The for line creates i and stores 1. The test 1 <= 4 is true, so the body runs.
Line 6i = 1total = 01 % 2 is 1, not 0, so the if test is false. The body of the if is skipped. The next row is the for line again.
Line 5i = 2total = 0i++ makes i 2. The test 2 <= 4 is true.
Line 6i = 2total = 02 % 2 is 0, so the test is true. Go into the body.
Line 7i = 2total = 2total = 0 + 2 stores 2.
Line 5i = 3total = 2i++ makes i 3. The test is true.
Line 6i = 3total = 23 % 2 is 1, so the test is false.
Line 5i = 4total = 2i++ makes i 4. The test 4 <= 4 is true, because <= includes 4.
Line 6i = 4total = 24 % 2 is 0, so the test is true.
Line 7i = 4total = 6total = 2 + 4 stores 6.
Line 5i = 5total = 6i++ makes i 5. The test 5 <= 4 is false, so the loop ends. The body does not run again.
Line 10i = –total = 6printf writes 6 and a new line. The variable i stopped existing when the loop ended, so it gets a dash again. The program prints 6.

Common mistakes

Wrong
/* meant: run the body 3 times */
for (int i = 0; i <= 3; i++) {
    printf("*");
}

The test uses <=, so the loop still runs when i is 3. The values of i are 0, 1, 2 and 3, which is four rounds. A trace table shows this at once: count the rows where the test is true. To run the body n times, start at 0 and use <.

Right
/* runs the body 3 times */
for (int i = 0; i < 3; i++) {
    printf("*");
}
Wrong
double half = 7 / 2;

Both 7 and 2 are ints, so C does integer division first. 7 / 2 is 3, and C then stores 3 as 3.0. The decimal part is already gone. Trace with the types the program has, not with the numbers you hoped for. Make at least one side a double: 7 / 2.0 is 3.5.

Right
double half = 7 / 2.0;
Wrong
int i = 3;
int a = i++;

i++ hands out the old value of i, and only then adds 1. So a is 3 and i is 4. If you want the new value, put the increment on its own line first. While you are learning, keep ++ on its own line, so that each change gets its own row in the table.

Right
int i = 3;
i++;
int a = i;
Wrong
a = b;
b = a;

After a = b, the old value of a is gone, so b = a copies b back onto itself. In the table, both columns hold the same number from that row on. Swapping needs a third place to keep one value safe.

Right
int t = a;
a = b;
b = t;

Check yourself

  1. Make a trace table for these four lines, numbered 1 to 4: int a = 5; int b = 2; a = a - b; b = a * b; What are a and b at the end?

    Try it first, then show the answer

    Line 1: a = 5, b = –. Line 2: a = 5, b = 2. Line 3: a = 3, b = 2. Line 4: a = 3, b = 6. At the end a is 3 and b is 6. Line 4 uses the new value of a (3), not the old 5.

  2. Predict, without running: what does for (int i = 3; i > 0; i--) printf("%d ", i); print? Which row ends the loop?

    Try it first, then show the answer

    It prints 3 2 1, each followed by a space. The test i > 0 is true for 3, 2 and 1. After the third round, i-- makes i 0, the test is false, and the loop ends. The value 0 is never printed.

  3. Change the worked program so that the if test is i % 2 == 1. Write the total column of the new trace table and the output.

    Try it first, then show the answer

    Now the body runs only for i = 1 and i = 3. The total column starts at 0, becomes 1 after i = 1, stays 1 for i = 2 (the test is false), becomes 4 after i = 3, and stays 4 for i = 4. The program prints 4.

02

Draw memory

Goal You can draw the stack, the heap and static data for a C program at any line, with one fixed convention, and say what free changes in the picture.

A program with pointers cannot be understood from the code alone. You need a picture of where each value lives and which pointer points where. On paper, this picture is your trace table for pointers. If you draw it the same way every time, you will make far fewer slips.

C keeps values in three places. Draw them as three columns, each with a heading: Static data, Stack, Heap.

Static data holds what exists for the whole run: string literals such as "hi", and global variables. Text written as a string literal is read-only. Writing to it is an error.

The stack holds one frame for each function that is running right now. A frame is a box that holds that function's parameters and local variables. Draw main's frame first, at the top of the Stack column. Each call adds a new frame below it. When a function returns, cross its frame out. Everything inside it is gone.

The heap holds blocks that you ask for with malloc. A block lives until you call free on it. No function frame owns it.

Now the rules for drawing things.

1. A variable is a small box. Write its type and name beside it and its value inside, such as int n and 3. A variable with no value yet holds ?.
2. A pointer is a box too. Its value is an arrow. The arrow starts inside the box and ends at the thing it points at. Also write the target's label inside the box, so you can still read it when arrows cross.
3. Give every heap block a label: H1, H2, H3, in the order malloc is called. Write the label, the type and the size on top, such as H1 (int[3]). Draw each cell of a block as a box with its index: [0], [1], [2]. Real addresses change from run to run, so a drawing uses labels instead.
4. An assignment copies what is in the box. p = q copies the arrow, not the block. After int *q = p; there are two boxes with arrows and still one block.
5. A string is an array of char cells that ends with a cell holding '\0'. char s[] = "hi" makes a three-cell array inside the frame. char *t = "hi" makes a pointer inside the frame whose arrow ends in read-only text in Static data.

What free does to the drawing. free(p) frees the block that p points at. On the page, draw a large X over the block and write freed. Leave the arrow and the label in p alone, because free does not change p. Every pointer that still holds that label is now a dangling pointer: it points at memory you no longer own. Reading or writing through it is undefined behaviour, which means the language makes no promise about what the program does. Calling free twice on the same block is also undefined behaviour. After free(p), write p = NULL; and draw a slash through the box of p. NULL is a pointer value that points at nothing, and free(NULL) does nothing.

A leak is a block that was never freed and that no arrow reaches any more. At the last line of main, look at each block. If it has no X and no arrow into it, it leaked.

New words

stack and frame
The stack holds one frame for each running function. A frame holds that call's parameters and local variables and is gone when the call returns.
heap
The memory blocks you get with malloc. They stay until you free them.
static data
Memory that exists for the whole run: string literals and global variables.
pointer, &x and *p
A pointer is a variable that holds an address. &x is the address of x. *p is the thing p points at.
label
A name such as H1 that you give to a heap block in your drawing, instead of a real address.
NULL
A pointer value that points at nothing. It is safe to compare with, and unsafe to read through.
dangling pointer
A pointer that still holds the label of a block that was freed, or of a frame that is gone.
undefined behaviour
A mistake after which the language promises nothing: the program may crash, print rubbish or seem to work.
leak
A heap block that was never freed and that no pointer reaches any more.

Worked example

C11
#include <stdio.h>
#include <stdlib.h>

int *make(int n) {
    int *a = malloc(n * sizeof(int));
    if (a == NULL) {
        return NULL;
    }
    for (int i = 0; i < n; i++) {
        a[i] = (i + 1) * 10;
    }
    return a;
}

int main(void) {
    int *p = make(3);
    if (p == NULL) {
        return 1;
    }
    int *q = p;
    q[1] = 99;
    /* A: draw the memory here */
    printf("%d %d %d\n", p[0], p[1], p[2]);
    free(p);
    p = NULL;
    /* B: draw the memory here */
    return 0;
}
Predict the output first, then show it

Expected output

10 99 30

Step by step

  1. Draw three columns with headings: Static data, Stack, Heap. This program has no globals and no string literals to keep, so Static data stays empty. (The format string "%d %d %d\n" is a string literal, so you may write it there.)

  2. main starts. Draw main’s frame at the top of Stack with two boxes: int *p holding ?, and int *q holding ?. Nothing has been stored yet.

  3. make(3) is called. Draw a new frame below main’s with two boxes: int n holding 3, and int *a holding ?. The argument 3 was copied, so n is a new box with its own 3.

  4. malloc(n * sizeof(int)) asks for room for 3 ints. Draw the first heap block: H1 (int[3]) with cells [0], [1], [2], all holding ?, because malloc does not set them. a gets an arrow to H1 and the label H1 inside its box.

  5. The loop stores 10, 20, 30 into the cells, with a[i] = (i + 1) * 10. While it runs, i is a third box inside make’s frame. When the loop ends, i is gone.

  6. return a copies the arrow out of make. Cross out make’s frame: n and a are gone. The block H1 stays, because a heap block does not belong to a frame. In main, p now holds H1: draw an arrow from p to H1.

  7. int *q = p; copies the arrow. q holds H1 and its arrow also ends at H1. There is still one block. q[1] = 99 changes that one block, so cell [1] now holds 99. At marker A the picture is: main has p = H1 and q = H1, and H1 holds 10, 99, 30.

  8. printf prints p[0], p[1] and p[2], which are 10, 99 and 30. The program prints 10 99 30. Notice that p[1] shows 99, although only q was used to write it.

  9. free(p) frees H1: draw a big X over the block and write freed. p still holds H1 and so does q, so both are now dangling. Then p = NULL; puts a slash through p’s box. At marker B: p is NULL, q still holds H1 and is dangling, and H1 is freed. The program does not use q again, so it is safe, but *q would be undefined behaviour. No block is left without an X, so there is no leak.

Common mistakes

Wrong
int *q = p;

p [H1] ----> H1 (int[3]) | 10 | 20 | 30 |
q [H2] ----> H2 (int[3]) | 10 | 20 | 30 |

This picture shows q = p copying the whole block. It does not: it copies the arrow. The drawing then makes you believe that writing through q leaves p alone. Look at the box you copy from. It holds a label, so the label is what you copy.

Right
int *q = p;

p [H1] ----+
           v
           H1 (int[3]) | 10 | 20 | 30 |
           ^
q [H1] ----+
Wrong
free(p);

p [NULL]
H1 (int[3]) | 10 | 20 | 30 |

free does not change p. This picture says that p became NULL and that the block is still fine. Both are wrong. p still holds H1, and the block is no longer yours. Only an assignment such as p = NULL changes p.

Right
free(p);

p [H1] ----> H1 (int[3]) FREED
             |  X  |  X  |  X  |
p is now dangling; write p = NULL
Wrong
char *s = "hi";
s[0] = 'H';

char *s only makes a pointer in the frame, and its arrow ends in read-only text in Static data. Writing to that text is undefined behaviour and usually crashes. char s[] makes a private three-cell copy inside the frame, and you may change it.

Right
char s[] = "hi";
s[0] = 'H';
Wrong
int *bad(void) {
    int x = 5;
    return &x;
}

x lives in bad’s frame, and that frame is crossed out when bad returns. The returned arrow then ends in a frame that no longer exists. Draw the two frames and you can see it. Return the value itself, or put the value in a heap block (a malloc block outlives the frame, but the caller must then free it).

Right
int good(void) {
    int x = 5;
    return x;
}

Check yourself

  1. Draw it: int a = 4; int *p = &a; *p = 9; Where does the arrow of p end, and what is a at the end? (&a means the address of a. *p means the thing p points at.)

    Try it first, then show the answer

    Both boxes are in main’s frame. The arrow of p ends at the box of a, on the stack, not in the heap. *p = 9 writes 9 into the box that p points at, which is a. So a is 9.

  2. Draw it: int *p = malloc(sizeof(int)); *p = 7; free(p); Is *p allowed on the next line? What should the next line be?

    Try it first, then show the answer

    Block H1 (int) holds 7 and is then crossed out and marked freed. The box p still holds H1, so p is dangling. Using *p is undefined behaviour. The next line should be p = NULL;.

  3. Find the leak: int *p = malloc(4 * sizeof(int)); p = malloc(8 * sizeof(int)); free(p); Draw the heap and say which block leaks and how to fix it.

    Try it first, then show the answer

    There are two blocks. The first malloc makes H1 (int[4]) and p holds H1. The second malloc makes H2 (int[8]) and the arrow of p moves to H2. No arrow reaches H1 any more and it was never freed, so H1 leaks. free(p) frees H2 only. Fix: call free(p) before the second malloc, or keep the first arrow in another pointer and free both.

03

Syntax to memorise

Goal You can write the eight C skeletons below from memory, shortest first, with every semicolon and bracket in place.

Some code cannot be worked out on the spot. You must know its shape without looking. The eight shapes in this lesson are the ones C needs again and again. They are short enough to learn by heart. Learn them in this order, shortest first, and add the next one only when the last is solid.

1. Includes. 2. main. 3. A function. 4. A for loop. 5. A struct with typedef. 6. malloc with a NULL check, and free. 7. String functions. 8. printf specifiers.

How to memorise: cover the page and write the shape on paper. Compare it with the page, character by character. Tick it only when it is right with no peeking. Do it again tomorrow. A shape that you write correctly three days in a row is learned.

The program below contains all eight shapes, each marked with a comment such as (1). Read it once. Then cover it and write each shape on its own, in a few lines of your own paper.

String functions: the destination always comes first, like the left side of an equals sign. strcpy(dest, src) copies src into dest. strcat(dest, src) adds src to the end of dest. strncpy(dest, src, n) and strncat(dest, src, n) take a limit n as the last argument. Be careful: strncpy does not write the '\0' when src is n characters or longer. strcmp(a, b) returns 0 when the strings are equal, a negative number when a comes before b, and a positive number when a comes after b. Because 0 means equal, write strcmp(a, b) == 0. strlen(s) gives the length without the '\0'. The destination must already have room for the text and the '\0'.

printf specifiers: %d an int, %u an unsigned int, %ld a long, %zu a size_t (what strlen returns), %f a double, %.2f a double with two decimal places, %c one char, %s a string, %p a pointer, %x an unsigned int in hexadecimal, %% a percent sign. Each specifier needs one argument of the matching type, in the same order.

New words

skeleton
The fixed shape of a piece of code, with the details left to fill in.
header
A file such as stdio.h that tells the compiler what a library function looks like.
format specifier
A code such as %d inside a printf format string that marks where a value goes.
destination
The array that receives the result of a string function. It is always the first argument.

Worked example

C11
/* (1) includes */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* (5) struct with typedef */
typedef struct {
    char name[20];
    int score;
} Player;

/* (3) a function */
int add(int a, int b) {
    return a + b;
}

/* (2) main */
int main(void) {
    /* (4) for */
    int total = 0;
    for (int i = 0; i < 3; i++) {
        total = add(total, i);
    }

    Player p;
    strcpy(p.name, "Mia");
    p.score = total;

    /* (6) malloc, NULL check, free */
    int *a = malloc(3 * sizeof(int));
    if (a == NULL) {
        return 1;
    }
    for (int i = 0; i < 3; i++) {
        a[i] = i * 10;
    }

    /* (7) string functions */
    char word[20];
    strcpy(word, "hello");
    strcat(word, "!");
    if (strcmp(word, "hello!") == 0) {
        /* (8) printf specifiers */
        printf("%s %d %c %.2f %zu\n", p.name, p.score, word[0], 3.14159, strlen(word));
    }

    free(a);
    return 0;
}
Predict the output first, then show it

Expected output

Mia 3 h 3.14 6

Step by step

  1. (1) Includes. #include <stdio.h> gives you printf. #include <stdlib.h> gives you malloc and free. #include <string.h> gives you strcpy, strcat, strcmp and strlen. Angle brackets, no semicolon. Before you finish, scan your code for each library function you used and tick off its header.

  2. (2) main. int main(void) { ... return 0; }. The return type is int. The word void between the round brackets says that main takes no arguments. return 0; means success. Check that the braces match.

  3. (3) A function. return_type name(type param, type param) { ... return value; }. Here it is int add(int a, int b) { return a + b; }. Every parameter needs its own type. The function must appear above main, or you must declare it above main with its first line followed by a semicolon.

  4. (4) for. for (int i = 0; i < n; i++) { ... }. Three parts in the round brackets, separated by two semicolons: start, test, step. There is no semicolon after the closing bracket. To run n times, start at 0 and use <.

  5. (5) struct with typedef. typedef struct { type field; type field; } Name; The semicolon after the closing brace is required. Use a dot to reach a field of a struct variable, as in p.score. Use an arrow when you have a pointer to a struct, as in ptr->score.

  6. (6) malloc, NULL check, free. type *p = malloc(count * sizeof(type)); if (p == NULL) { return 1; } ... free(p); Write the count times sizeof of the element type. Test for NULL straight after malloc, because malloc returns NULL when it cannot give you the memory. Each malloc needs exactly one free.

  7. (7) String functions. Destination first: strcpy(dest, src), strcat(dest, src). Compare with strcmp(a, b) == 0. The length is strlen(s). The array word has 20 cells, which is plenty for "hello!" (6 characters and the \0).

  8. (8) printf. printf("format", arg1, arg2, ...); one specifier for each argument, in order. Here %s is p.name, %d is p.score, %c is word[0], %.2f is 3.14159 and %zu is strlen(word). The program prints Mia 3 h 3.14 6.

Common mistakes

Wrong
char buf[10];
strcpy("hi", buf);

The destination is the first argument, like the left side of x = y. Here the text "hi" sits in read-only static data, and the call tries to write into it. Say it aloud as you write: copy into the first, from the second.

Right
char buf[10];
strcpy(buf, "hi");
Wrong
if (strcmp(a, b)) {
    printf("same\n");
}

strcmp returns 0 when the strings are equal, and an if treats 0 as false. So this prints "same" only when the strings are different. Always compare the result with 0 yourself.

Right
if (strcmp(a, b) == 0) {
    printf("same\n");
}
Wrong
printf("%d %s\n", 3.5, 'a');

%d expects an int but gets a double, and %s expects a string but gets a single char. The output is rubbish, or the program crashes. Match each specifier to the type of its argument: %f for a double, %c for a char.

Right
printf("%f %c\n", 3.5, 'a');
Wrong
int *a = malloc(3);

malloc counts bytes, not ints. Three bytes is too small for three ints (usually 12 bytes), so writing a[1] goes past the end of the block. Always write the count times sizeof of the element type.

Right
int *a = malloc(3 * sizeof(int));

Check yourself

  1. On paper, write from memory a function max2 that takes two ints and returns the larger one. Then compare it with the function skeleton.

    Try it first, then show the answer

    int max2(int a, int b) { if (a > b) { return a; } return b; } Check: the return type int, a type for both parameters, matching braces, a semicolon after each return, and no semicolon after the round brackets of the if or after the closing brace of the function.

  2. Write the three lines that declare a char array named color, copy "red" into it, and then add "dish" to its end. How many cells must color have?

    Try it first, then show the answer

    char color[8]; strcpy(color, "red"); strcat(color, "dish"); It needs 8 cells: reddish has 7 characters, plus one for the \0.

  3. Write the line that asks for room for 5 doubles, then the two lines that handle failure, and then the line that gives the memory back.

    Try it first, then show the answer

    double *d = malloc(5 * sizeof(double)); if (d == NULL) { return 1; } ... and later free(d); The NULL check comes straight after malloc, and free comes after the last use.

04

Common handwriting mistakes

Goal You can read your own C answer a second time, as the compiler would, and catch the slips that would otherwise cost marks.

On a computer, the compiler stops you at the first missing semicolon. On paper, nobody stops you, and every one of these slips costs marks even though it has nothing to do with whether you understand the problem. So you must be your own compiler.

When your answer is finished, read it a second time. This time do not ask whether the logic is right. Ask only whether the form is right. Use this list, and tick each item with your pen.

1. Every statement ends with a semicolon. A struct definition ends with one too, after the closing brace.
2. Every ( [ { has its partner. Count the braces in pairs.
3. Every variable is declared, with a type, before you use it.
4. Every library function has its header.
5. = stores a value. == asks a question. Check every if and while.
6. Strings use double quotes. A single char uses single quotes.
7. Every printf specifier has an argument of the right type.
8. Array indexes run from 0 to n - 1. A string of n characters needs n + 1 cells.
9. A dot reaches a field of a struct. An arrow reaches a field through a pointer.
10. A for or while line has no semicolon after its round brackets.

Below are four pairs that cost the most marks. In each pair the first block is what people write, and the second block is the repair.

Common mistakes

Wrong
typedef struct {
    int x;
    int y;
} Point

for (int i = 0; i < 3; i++);
{
    printf("%d\n", i);
}

Two semicolon slips. A struct definition needs a semicolon after its closing brace; without it the compiler reports an error on the next line. And a semicolon right after the for brackets is an empty body: the loop runs three times doing nothing, and the braces below it run once (and cannot even see i).

Right
typedef struct {
    int x;
    int y;
} Point;

for (int i = 0; i < 3; i++) {
    printf("%d\n", i);
}
Wrong
if (n = 0) {
    printf("empty\n");
}
char c = "a";

if (n = 0) stores 0 into n, and 0 is false, so the body never runs and n is changed. Use == to ask a question. And "a" is a string (two cells: a and \0), while 'a' is one char. A char variable needs single quotes.

Right
if (n == 0) {
    printf("empty\n");
}
char c = 'a';
Wrong
Node *p = head;
printf("%d\n", p.value);
int n;
scanf("%d", n);

p is a pointer to a struct, so its field is reached with an arrow, p->value, not a dot. And scanf needs the address of n, written &n, so that it can store the number there. Without &, scanf treats the value of n as an address, and the program usually crashes.

Right
Node *p = head;
printf("%d\n", p->value);
int n;
scanf("%d", &n);
Wrong
char s[3] = "abc";
if (s == "abc") {
    printf("same\n");
}

Two string slips. "abc" needs four cells, three letters and the \0; with three cells there is no end marker, and printf("%s") would read past the array. And == compares two addresses, not the letters, so it is almost never true. Compare letters with strcmp(s, "abc") == 0, and include <string.h>.

Right
char s[4] = "abc";
if (strcmp(s, "abc") == 0) {
    printf("same\n");
}

Check yourself

  1. Find every slip (there are four): typedef struct { int x } Pt; int main(void) { Pt p; p.x = 1; if (p.x = 1) { printf("one\n") } return 0; }

    Try it first, then show the answer

    1. int x needs a semicolon: int x;. 2. The if stores instead of asking: write p.x == 1. 3. printf("one\n") needs a semicolon after it. 4. printf needs #include <stdio.h> at the top. Working through the ten-item list in order finds all four.

  2. Fix the string handling: char name[4] = "Emma"; if (name == "Emma") { puts("same"); }

    Try it first, then show the answer

    char name[5] = "Emma"; (four letters plus the \0) and if (strcmp(name, "Emma") == 0) { puts("same"); } with #include <string.h> at the top.

  3. Fix the loop over a list of nodes: Node *n = head; while (n != NULL); { n = n.next; }

    Try it first, then show the answer

    There are two slips. The semicolon after the while brackets makes an empty loop that never ends, because n never changes. And n is a pointer, so the field is n->next. Correct: while (n != NULL) { n = n->next; }

05

Mark yourself

Goal You can mark your own paper answer honestly against a rubric, find where the marks went, plan tomorrow's practice, and write under time.

On paper, a page of writing can look finished and still be wrong. Marking yourself fixes that. You compare your answer with a rubric. A rubric is a list of lines, each worth a number of marks, each describing something that you can see in the answer. Be strict. The mark that counts is the one a stranger would give.

How to read a rubric, line by line.

1. Read one line. Ask: what exactly would I see in a correct answer?
2. Find that thing in your answer and touch it with the tip of your pen. If you can point to it, tick the line and write its marks. If you cannot point to it, the line is worth zero.
3. Do not give half marks for "nearly". Give part of a line only if the rubric says how it splits.
4. Check every path. A line such as "frees the block" is zero if one route through your code, for example an early return, skips the free.
5. Add up the lines. Write the total as marks out of the maximum on the page.

How to find where the marks went. Put each lost mark into one of four boxes. Idea: I did not know how to solve it. Recall: I knew, but could not remember the syntax or the function. Slip: a small mistake in something I did know, such as a missing semicolon or a wrong sign. Time: I ran out of time. Count each box. The biggest box tells you what to work on. Idea: reread the lesson. Recall: more cover-and-write practice. Slip: the compiler pass from the last lesson and a tiny test. Time: practise planning.

What to do the next day. First, rewrite the same answer on a blank page without looking. Second, do a different question of the same kind. Third, mark both again. Repeat after three days and after a week. When the biggest box changes, you are improving.

How to write under time.

1. Plan first. Use the first tenth of the time. Write the function header and three or four comment lines that name the steps. Do not skip this.
2. Leave a blank line between lines of code. You will need room to insert a line you forgot.
3. Name variables clearly: count, not c; head, not h. Clear names stop you from mixing up two variables.
4. Test with a tiny input, by hand. When the code is written, run it on the smallest case: one element, an empty string, n equal to 1. This takes a minute and finds most boundary errors.
5. Spend about one minute per mark. If you are stuck on a part, write a comment saying what you would do, move on, and come back.
6. Leave two minutes for the compiler pass.

On the Practice page, the questions of kind Write code have a Paper mode with a timer, a model answer and a rubric with tick boxes. Use it exactly like this lesson: write on paper, then mark.

New words

rubric
A list of lines, each worth some marks, each describing something you can point to in the answer.
boundary case
The smallest or most extreme input, such as one element or an empty string, where code most often breaks.
compiler pass
A second reading of your answer that checks only the form: semicolons, brackets, types, headers.

Worked example

C11
#include <stdio.h>

/* A student's answer. It has one bug. */
int smallest(const int *a, int n) {
    int best = 0;
    for (int i = 0; i < n; i++) {
        if (a[i] < best) {
            best = a[i];
        }
    }
    return best;
}

int main(void) {
    int test[] = {5, 3, 8};
    printf("%d (expected 3)\n", smallest(test, 3));
    return 0;
}
Predict the output first, then show it

Expected output

0 (expected 3)

Step by step

  1. The task: write int smallest(const int *a, int n), which returns the smallest of n ints (n is at least 1). The rubric has four lines, one mark each. (1) The header is right: it returns int and takes the array and the count. (2) The search starts from a[0], not from a made-up number. (3) The loop visits every element and none outside the array. (4) It updates when it finds a smaller value, and returns the result.

  2. Mark line 1. The header int smallest(const int *a, int n) is right. Put the pen on it. +1.

  3. Mark line 2. The answer starts best at 0. The pen has nothing to point at that starts from a[0]. 0 marks.

  4. Mark line 3. The loop for (int i = 0; i < n; i++) visits a[0] to a[n - 1], and stops before a[n]. +1.

  5. Mark line 4. if (a[i] < best) best = a[i]; and return best; are both there. +1.

  6. Score: 3 out of 4. Now the tiny test by hand: {5, 3, 8}. best starts at 0. The tests 5 < 0, 3 < 0 and 8 < 0 are all false, so the function returns 0, not 3. The program prints 0 (expected 3), which confirms it. The lost mark goes in the Idea box: the start value was not thought through. The fix is int best = a[0]; and a loop that starts at i = 1.

Common mistakes

Wrong
for (int i = 1; i < n - 1; i++) {
    if (a[i] < best) {
        best = a[i];
    }
}

The test i < n - 1 stops one element early, so the last element is never looked at. A tiny test with the smallest value in the last place finds this at once: {5, 8, 3}. Check the first and the last element every time.

Right
for (int i = 1; i < n; i++) {
    if (a[i] < best) {
        best = a[i];
    }
}
Wrong
int sum;
for (int i = 0; i < n; i++) {
    sum = sum + a[i];
}

sum was declared but never given a value, so it holds a question mark in your trace table, and the first addition reads it. The total is rubbish. Give every total a starting value on the line where you declare it.

Right
int sum = 0;
for (int i = 0; i < n; i++) {
    sum = sum + a[i];
}
Wrong
int total(const int *a, int n) {
    int sum = 0;
    for (int i = 0; i < n; i++) {
        sum = sum + a[i];
    }
    printf("%d\n", sum);
}

The task said "returns the total", but this function prints it and returns nothing. The caller gets rubbish. This is a rubric slip: read the task for the exact words returns, prints and reads, and underline them before you write.

Right
int total(const int *a, int n) {
    int sum = 0;
    for (int i = 0; i < n; i++) {
        sum = sum + a[i];
    }
    return sum;
}

Check yourself

  1. A rubric line says "frees the block on every path". Your function frees it at the end, but an early return skips the free. How many marks does that line get, and why?

    Try it first, then show the answer

    Zero, unless the rubric says how to split the mark. Put the pen on the free of the early-return path. There is none. Test every path through your code, not only the main one.

  2. You lost three marks: one for not knowing where to put & in scanf, one for a missing semicolon, and one because you ran out of time. Which box does each go in, and what do you do tomorrow?

    Try it first, then show the answer

    Recall, Slip, Time. Tomorrow: write the scanf line from memory three times, use the ten-item compiler pass on every answer, and plan for the first tenth of the time before writing.

  3. Write the tiny tests that you would run by hand on a function that returns the length of a string.

    Try it first, then show the answer

    The empty string (length 0), a one-character string (length 1), and a normal word. If the task allows it, also NULL. Run each through your code, line by line, with a trace table.

What next

Practise on paper: open the Write code questions for COMP2017, set a timer, and mark yourself against the rubric. →

Independent study notes, not official course material.