LW Leon Learn

C first steps: from your first program to pointers

Eight short lessons for someone who has never programmed: write and run a first program, print values, use variables, decisions and loops, functions, arrays and strings, pointers, and read compiler messages. Every example also runs on this page.

What you need first

  • No programming experience. If you have never written code, start at lesson 1. This guide assumes nothing.
  • School arithmetic: adding, multiplying, dividing, and remainders (7 divided by 2 is 3, remainder 1).
  • Knowing what a file and a folder are. The few terminal commands you need are taught in lesson 1.
  • About 30 minutes per lesson. Type the examples yourself. Reading them is not enough.
  • Nothing installed to begin. Every example has a Run button on this page. Set up a real compiler (next section) when you want to work on your own computer.

Set up your computer

Optional at first: the examples below also run on this page.

You do not need to install anything to start. Each example below has a Run button that compiles and runs the C program inside your browser. Use it for the first lessons.

You will still want a real compiler on your own computer. A compiler is a program that turns your C text into a program the computer can run. There are two reasons. First, the course you move on to compiles programs from a terminal, so you need the habit. Second, C in the browser does not catch memory errors. A program that reads or writes outside the memory it owns can run on this page and print a normal-looking answer, even though it is wrong. A real compiler with sanitizers (lesson 8) stops such a program and tells you the line. Installing one takes about ten minutes.

Pick your system, follow the steps, then run the check command. A terminal is a window where you type commands and press Return to run them.

macOS
  1. Open the Terminal app. Press Command and Space together, type Terminal, and press Return.

  2. Type this line and press Return:
    xcode-select --install

  3. A window asks to install the command line developer tools. Click Install and agree to the licence. Choose Install, not Get Xcode: you do not need the full Xcode app. The download takes several minutes. If the terminal says the tools are already installed, you already have them.

  4. When it finishes, close the Terminal window and open a new one. Then run the check command below.

Check it worked

cc --version

You should see Several lines. The first starts with Apple clang version and a number. A line starting with Target names your chip, such as arm64-apple-darwin or x86_64-apple-darwin. The numbers differ between computers.

Linux (Ubuntu, Debian and similar)
  1. Open a terminal. On many desktops the shortcut is Ctrl, Alt and T together.

  2. Refresh the list of available software. Type this and press Return:
    sudo apt update
    sudo means "run as administrator". It asks for your password and shows nothing while you type. That is normal.

  3. Install the compiler and the tools around it:
    sudo apt install build-essential
    Answer Y if it asks whether to continue. The package build-essential brings gcc, the standard C library and the make tool.

  4. On Fedora, use this line instead:
    sudo dnf install gcc make

Check it worked

cc --version

You should see A few lines. The first names the compiler and a version number, for example cc (Ubuntu 13.2.0-23ubuntu4) 13.2.0. Yours will differ.

Windows (through WSL)
  1. WSL means Windows Subsystem for Linux. It gives you a real Linux terminal inside Windows, and the Linux steps then work unchanged. You need Windows 10 version 2004 or newer, or Windows 11.

  2. Click Start, type PowerShell, right-click Windows PowerShell and choose Run as administrator.

  3. Type this and press Enter:
    wsl --install
    This installs WSL and Ubuntu, the default Linux system. If it only prints a list of options, WSL is already installed. Run this instead:
    wsl --install -d Ubuntu

  4. Restart the computer when it asks you to.

  5. After the restart, an Ubuntu window opens by itself. If it does not, open Start, type Ubuntu and open it. The first start takes a few minutes. It then asks you to choose a username and a password. The password shows nothing while you type.

  6. In the Ubuntu window, follow the Linux steps: sudo apt update, then sudo apt install build-essential.

  7. From now on, do all your C work in the Ubuntu window, not in PowerShell. The folder where Ubuntu starts is fine to work in.

Check it worked

cc --version

You should see Run it in the Ubuntu window. You should see the same kind of output as on Linux: a first line with cc, Ubuntu and a version number.

If you see an error

  • implicit declaration of function 'printf'

    The compiler met a function name it was never told about. Almost always you forgot the line #include <stdio.h> at the top of the file, because printf is described there. Other causes are a misspelled function name, or calling your own function before it is defined (lesson 5 shows the fix).

    On a Mac the same mistake reads: call to undeclared library function 'printf'. Older versions of gcc print only a warning here and build the program anyway, which then behaves strangely. Treat it as an error either way.

  • undefined reference to `main'

    The compiler read your file but found no function named main. A C program always starts in main, so without it there is nothing to run. Check the spelling: man, Main and mian are different names. Check that you are compiling the file you meant. Check that main was not placed inside the braces of another function.

    On a Mac the same problem reads: Undefined symbols for architecture arm64, followed by "_main".

  • command not found

    The terminal could not find a program with that name. There are three common causes.

    One: you typed hello when you meant ./hello. The terminal does not look in the current folder unless you put ./ in front of the name. Two: the compiler is not installed, so cc is unknown. Repeat the setup steps for your system. Three: you installed it a moment ago. Close the terminal window and open a new one.

    The full message looks like zsh: command not found: cc on a Mac, and bash: cc: command not found on Linux. On Windows, if you see "cc is not recognized as the name of a cmdlet", you are in PowerShell. Open the Ubuntu window instead.

  • Segmentation fault

    The program touched a memory address that does not belong to it, and the system stopped it. Typical causes are a pointer that points nowhere (lesson 7), an array index outside the array (lesson 6), and a missing & before a variable in scanf.

    The message is Segmentation fault (core dumped) on Linux and zsh: segmentation fault on a Mac. Some systems say Bus error instead. Treat it the same way.

    To find the line, compile with -g -fsanitize=address,undefined and run the program again. The sanitizer prints the file and line number (lesson 8).

  • No such file or directory

    The file name or the folder is not what you think. Type this to see which folder you are in:
    pwd
    Type this to list the files in it:
    ls
    If your file is not in the list, use cd to move to the right folder. Check the spelling and the .c ending too.

01

Source, compile, run: your first program

Goal After this you can write a small C program, compile it, run it, and say what source, compile and run each mean.

A computer follows instructions. A programming language is a way to write instructions so that people can read them. C is one such language. It is old, small and fast, and many operating systems are written in it.

Getting a C program to work has three steps, and each has a name.

Source code is the text you write. You save it in a plain text file whose name ends in .c, for example hello.c. The computer cannot follow this text directly.

To compile is to translate the source code into machine code, the numbers a processor can follow. The tool that does the translating is the compiler. It reads hello.c and writes a new file, the executable, which is a file the computer can run. While compiling, the compiler also checks your text and stops if it finds a serious mistake.

To run is to ask the computer to carry out the executable. The program prints its output and then stops.

You repeat the cycle many times: edit the source, compile, run. If you change the source but forget to compile, you run the old program.

Every C program starts in a function called main. A function is a named group of instructions. The first line of the example, the one with #include, brings in the description of printf, the function that prints text. The curly brackets { } mark where main begins and ends. Each instruction inside is a statement, and a statement ends with a semicolon.

On your own computer you do this in a terminal. Four commands are enough for now. To see which folder you are in, type:
pwd
To list the files in the folder, type:
ls
To make a new folder called c-practice, type:
mkdir c-practice
To move into that folder, type:
cd c-practice

Press Return after each command. Now create the file with nano, an editor that runs inside the terminal:
nano hello.c
Type the program, press Ctrl and O together and then Return to save, and press Ctrl and X to leave. Any plain text editor works as well. Do not use a word processor, because it adds hidden formatting. Then compile and run:
cc -std=c11 -Wall -Wextra hello.c -o hello
./hello

Here cc is the compiler. hello.c is the source. The part -o hello means "name the executable hello". Take care here: the name after -o is the file the compiler creates, and it replaces any file with that name. Never write your .c file there. The options -Wall and -Wextra ask the compiler to point out suspicious code, and lesson 8 explains them. The text ./hello means "the program called hello in this folder". If the compile line prints nothing, it worked.

You can also press the Run button on the example below. It does the compile and run steps for you inside the page.

New words

source code
The text of a program, saved in a file such as hello.c.
compiler
A program that translates source code into an executable and reports mistakes it finds.
executable
The file the compiler writes. The computer can run it directly.
terminal
A window where you type commands and read the answers.
function
A named group of instructions. Every C program starts in the function main.
statement
One instruction. In C it ends with a semicolon.

Worked example

C11
// hello.c: my first program
#include <stdio.h>

int main(void) {
    printf("Hello, Leon!\n");
    printf("This program ran.\n");
    return 0;
}
Predict the output first, then show it

Expected output

Hello, Leon!
This program ran.

Step by step

  1. The first line starts with //. Everything after // on a line is a comment: a note for people that the compiler ignores.

  2. #include <stdio.h> brings in the description of printf. The name stdio stands for standard input and output. Without this line the compiler does not know what printf is.

  3. int main(void) begins the function main. The word int says that main hands back a whole number when it ends. The word void says that main takes nothing in.

  4. The { opens the body of main and the matching } closes it. Everything between them belongs to main.

  5. printf("Hello, Leon!\n"); prints the text between the quote marks. The two characters \n stand for "start a new line". The semicolon ends the statement.

  6. The second printf prints a second line. Statements run from top to bottom, one after another.

  7. return 0; ends main. The 0 tells the system that the program succeeded.

Common mistakes

Wrong
printf("Hello, Leon!\n")
return 0;

There is no semicolon at the end of the printf line. The compiler stops with an error such as: expected ';' after expression. Some compilers, gcc among them, can report it on the next line, because that is where they notice the statement did not end.

Right
printf("Hello, Leon!\n");
return 0;
Wrong
hello

The terminal answers command not found. It does not look in the current folder unless you tell it to. Put ./ in front of the name.

Right
./hello
Wrong
printf("Hello, Leon!");

Without \n the text stays on the same line, and the terminal prompt appears right after it: Hello, Leon!$ and then your cursor. Add \n at the end of the text.

Right
printf("Hello, Leon!\n");
Wrong
(edit hello.c, save it)
./hello

You changed the source but ran the old executable. The output does not change until you compile again. Compile first, and run only if the compile worked.

Right
cc -std=c11 -Wall -Wextra hello.c -o hello && ./hello

Check yourself

  1. Write a program that prints your name on the first line and the words Welcome to C on the second line.

    Try it first, then show the answer

    One answer, with the name Mia:

    #include <stdio.h>

    int main(void) {
    printf("Mia\n");
    printf("Welcome to C\n");
    return 0;
    }

    Each \n ends a line. Without the first \n, both pieces of text would sit on one line.

  2. Predict the output of printf("A"); printf("B\n"); printf("C\n");

    Try it first, then show the answer

    Two lines. The first is AB, because the first printf has no \n and the second continues on the same line. The second line is C.

  3. The compiler says: implicit declaration of function 'printf'. Which line is missing from the program, and where does it go?

    Try it first, then show the answer

    The line #include <stdio.h>. It goes at the top of the file, before main, because printf is described in that header.

02

printf and format specifiers: printing values

Goal After this you can print numbers, letters and text with printf, and pick the right specifier for each.

printf prints text. The first thing you give it is the format string, a piece of text in double quotes. Most of it is printed as written. But a percent sign followed by a letter is a placeholder, called a format specifier. printf replaces each placeholder with the next value you list after the format string, in order.

For example, this line prints Leon is 20 years old:
printf("Leon is %d years old\n", 20);
The %d is the placeholder for a whole number. The 20 is the value that fills it. The values after the format string are called arguments.

These specifiers cover most needs:
%d a whole number (type int)
%f a number with a decimal point (type double). Writing %.2f shows exactly two digits after the point.
%c one character, such as A
%s a string, which is text such as Mia
%x a whole number in hexadecimal, which is base 16 with the digits 0 to 9 and a to f. The number 255 prints as ff.
%zu a size, the kind of number that sizeof produces (lesson 3)
%% a percent sign itself

You can put a width between the % and the letter. %5d pads the number with spaces to at least 5 characters, and %-5d does the same but keeps the number at the left.

The specifier must match the value. printf cannot check the type of a value. It trusts you. If you write %d and pass a number with a decimal point, the output is nonsense, and only the compiler's warnings (lesson 8) can catch it. The number of placeholders must also equal the number of values.

Special characters inside the quotes start with a backslash. This is called an escape sequence. \n is a new line, \t is a tab, \" is a quote mark and \\ is a backslash.

Double quotes make text. Single quotes make one character: 'A' is the letter A and "A" is a piece of text. Lesson 3 and lesson 6 return to this.

New words

format string
The first argument of printf. It holds the text and the placeholders.
format specifier
A placeholder that starts with %, such as %d or %.2f.
argument
A value you hand to a function, here the values after the format string.
escape sequence
A backslash and a letter that stand for one special character, such as \n for a new line.
hexadecimal
Counting in base 16. After 9 come a, b, c, d, e, f, so 255 is ff.

Worked example

C11
#include <stdio.h>

int main(void) {
    int apples = 3;
    double price = 2.5;
    char grade = 'A';

    printf("Leon bought %d apples.\n", apples);
    printf("Each costs %.2f, so the total is %.2f.\n", price, apples * price);
    printf("Grade: %c\n", grade);
    printf("Name: %s\n", "Mia");
    printf("Hex: %x, character code: %d\n", 255, grade);
    printf("Padded: [%5d] [%-5d]\n", 42, 42);
    printf("Ratio: 50%%\n");
    printf("A quote \" and a backslash \\\n");
    return 0;
}
Predict the output first, then show it

Expected output

Leon bought 3 apples.
Each costs 2.50, so the total is 7.50.
Grade: A
Name: Mia
Hex: ff, character code: 65
Padded: [   42] [42   ]
Ratio: 50%
A quote " and a backslash \

Step by step

  1. Three variables are declared first. A variable holds a value (lesson 3 explains them). Here apples holds 3, price holds 2.5 and grade holds the letter A.

  2. %d is filled by apples, so the line reads Leon bought 3 apples.

  3. %.2f appears twice. The first takes price and prints 2.50. The second takes apples * price, which is worked out first (3 times 2.5 is 7.5) and prints as 7.50.

  4. %c prints the single character A. %s prints the text Mia without its quote marks.

  5. 255 in hexadecimal is ff. The second value, grade, is printed with %d, so you see 65: the number the computer uses to store the letter A.

  6. %5d puts 42 in a field 5 characters wide, with spaces on the left. %-5d puts the spaces on the right. The square brackets make the width visible.

  7. %% prints one percent sign. Inside the text, \" prints a quote mark and \\ prints one backslash.

Common mistakes

Wrong
printf("%d\n", 2.5);

%d expects a whole number, but 2.5 has a decimal point. The output is nonsense and changes from machine to machine. With -Wall the compiler warns: format specifies type 'int' but the argument has type 'double' [-Wformat]. gcc words it as: format '%d' expects argument of type 'int', but argument 2 has type 'double'.

Right
printf("%f\n", 2.5);
Wrong
printf("%d and %d\n", 7);

There are two placeholders but only one value. The second %d prints whatever happens to be lying around. The compiler warns: more '%' conversions than data arguments.

Right
printf("%d and %d\n", 7, 8);
Wrong
printf('Hello\n');

Single quotes are for one character. Text needs double quotes. The compiler refuses this line or warns about it, and the program cannot print the text.

Right
printf("Hello\n");
Wrong
printf("Sale: 50% off\n");

A lone % starts a placeholder. Here "% o" is read as a specifier that wants a value, so the output is broken. To print a percent sign, write it twice.

Right
printf("Sale: 50%% off\n");

Check yourself

  1. Write one printf that prints Iris scored 93.5% : take the name from %s, show the score with one digit after the point, and print a real percent sign at the end.

    Try it first, then show the answer

    printf("%s scored %.1f%%\n", "Iris", 93.5);

    %s takes "Iris", %.1f takes 93.5 and prints 93.5, and %% prints the percent sign.

  2. Predict the output of printf("[%4d][%-4d][%c]\n", 7, 7, 66); The letter B has the code 66.

    Try it first, then show the answer

    [ 7][7 ][B]. The first field is 4 wide with the 7 at the right. The second has the 7 at the left. %c turns the code 66 into the character B.

  3. Fix this line so it prints Total: 4.75 : printf("Total: %d\n", 4.75);

    Try it first, then show the answer

    Use %f for a number with a decimal point: printf("Total: %.2f\n", 4.75); The .2 asks for two digits after the point.

03

Variables, types and sizes

Goal After this you can declare variables, choose between int, double and char, read a simple arithmetic expression, and find how many bytes a type takes.

A variable is a named box in the computer's memory that holds one value. You make one by declaring it: write the type, then the name, then usually a starting value.
int count = 7;

The type says what kind of value fits in the box and how big the box is. You need three types for now. An int is a whole number such as -3, 0 or 42. A double is a number with a decimal point, such as 3.14. A char is one character, such as 'A', written in single quotes.

The sign = means assign. It works out the right side and stores the result in the box on the left. It is not the equals of mathematics. The statement count = count + 1 means "take the value in count, add one, and store it back". The statement count += 2 is a short way to write count = count + 2.

Always give a variable a starting value. A box that you never filled holds leftover bits, and reading it gives unpredictable results.

An expression is a piece of code that works out to a value, such as 2 + 3 * 4. To read one, find which part is worked out first. Brackets come first. Then come *, / and % (multiply, divide, remainder), and then + and -. Operators of the same level go from left to right. So 2 + 3 * 4 is 14, and (2 + 3) * 4 is 20. When you are unsure, add brackets.

There are two traps. First, dividing an int by an int gives an int, and the fraction is thrown away. So 7 / 2 is 3. The % operator gives the remainder, so 7 % 2 is 1. If you want 3.5, make one side a double, as in 7 / 2.0. Second, an int has a limited range, roughly minus 2 billion to plus 2 billion. The header limits.h provides INT_MAX, the largest int. Going past it is undefined behaviour, which means the language makes no promise at all about what happens.

Memory is counted in bytes. A bit is a single 0 or 1, and a byte is a group of bits, 8 on ordinary computers. The operator sizeof gives the number of bytes that a type or variable takes. On ordinary computers a char takes 1, an int takes 4 and a double takes 8. The constant CHAR_BIT, from limits.h, is the number of bits in a byte.

A char is stored as a small number: 'A' is 65. That is why 'A' + 1 is 66, the code of 'B'.

New words

variable
A named box in memory that holds one value of one type.
declaration
The line that creates a variable: type, name and usually a starting value.
assignment
Storing a value in a variable with =. The right side is worked out first.
expression
A piece of code that works out to a value, such as 2 + 3 * 4.
byte
The unit memory is counted in. It holds CHAR_BIT bits, 8 on ordinary computers.
undefined behaviour
Code for which the language promises nothing. The program may crash, print nonsense or seem to work.

Worked example

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

int main(void) {
    int count = 7;
    double ratio = 7.0 / 2;
    char letter = 'A';

    printf("count = %d, ratio = %.1f\n", count, ratio);
    printf("7 / 2 = %d, 7 %% 2 = %d, 7 / 2.0 = %.1f\n", 7 / 2, 7 % 2, 7 / 2.0);
    printf("2 + 3 * 4 = %d, (2 + 3) * 4 = %d\n", 2 + 3 * 4, (2 + 3) * 4);

    count = count + 1;
    count += 2;
    printf("count is now %d\n", count);

    printf("letter %c has code %d, and letter + 1 is %c\n", letter, letter, letter + 1);
    printf("sizes: char %zu, int %zu, double %zu\n", sizeof(char), sizeof(int), sizeof(double));
    printf("bits in a byte: %d, largest int: %d\n", CHAR_BIT, INT_MAX);
    return 0;
}
Predict the output first, then show it

Expected output

count = 7, ratio = 3.5
7 / 2 = 3, 7 % 2 = 1, 7 / 2.0 = 3.5
2 + 3 * 4 = 14, (2 + 3) * 4 = 20
count is now 10
letter A has code 65, and letter + 1 is B
sizes: char 1, int 4, double 8
bits in a byte: 8, largest int: 2147483647

Step by step

  1. Three variables are declared with starting values. 7.0 / 2 has a double on its left, so the division keeps the fraction and ratio holds 3.5.

  2. 7 / 2 divides two ints and gives 3. 7 % 2 is the remainder, 1. 7 / 2.0 has a double on the right, so the answer is 3.5. In the text, %% prints a percent sign.

  3. Multiplication goes before addition, so 2 + 3 * 4 is 2 + 12, which is 14. The brackets in (2 + 3) * 4 force the addition first, giving 20.

  4. count = count + 1 changes 7 to 8. count += 2 changes 8 to 10.

  5. letter is printed twice: %c shows the character A and %d shows its code, 65. The expression letter + 1 is the number 66, and %c shows it as B.

  6. sizeof(char), sizeof(int) and sizeof(double) give 1, 4 and 8. They are printed with %zu, the specifier for sizes.

  7. CHAR_BIT is the number of bits in a byte. INT_MAX is the largest value an int can hold on this computer. Both come from limits.h.

Common mistakes

Wrong
double half = 1 / 2;

Both sides of / are ints, so the division runs as integer division first and gives 0. Only then is 0 stored as 0.0. Putting the answer in a double does not help. Make one side a double before the division.

Right
double half = 1 / 2.0;
Wrong
int total;
total = total + 5;

total was never given a value, so the 5 is added to leftover bits. The result is unpredictable. The compiler warns only if you ask it to: with -Wall, clang says variable 'total' is uninitialized when used here. gcc may show this warning only when you also add -O1.

Right
int total = 0;
total = total + 5;
Wrong
char letter = "A";

Double quotes make text, which is a row of characters, not one character. The compiler complains about converting a pointer to a char. A single character uses single quotes.

Right
char letter = 'A';
Wrong
int big = 2147483647;
big = big + 1;

2147483647 is INT_MAX on ordinary computers. Adding 1 goes past the range, which is undefined behaviour. You may see -2147483648, or anything else. Check before you add, or use a wider type.

Right
int big = 2147483647;
if (big < INT_MAX) {
    big = big + 1;
}

Check yourself

  1. Predict the output of int a = 17; printf("%d %d %.1f\n", a / 5, a % 5, a / 5.0);

    Try it first, then show the answer

    3 2 3.4. Dividing ints gives 3, the remainder of 17 divided by 5 is 2, and 17 divided by 5.0 is 3.4.

  2. Write a program body that declares a double price set to 4.5 and an int qty set to 3, then prints the total with two digits after the point.

    Try it first, then show the answer

    double price = 4.5;
    int qty = 3;
    printf("Total: %.2f\n", price * qty);

    It prints Total: 13.50. The int qty is turned into a double for the multiplication.

  3. Predict the output of printf("%c %zu\n", 'A' + 2, sizeof(int) * 3);

    Try it first, then show the answer

    C 12. 'A' + 2 is the number 67, which %c shows as C. sizeof(int) is 4, and 4 times 3 is 12.

04

Decisions and repetition: if and loops

Goal After this you can make a program choose with if, repeat work with for and while, and read the conditions that control them.

A program normally runs its statements from top to bottom. The statements if and the loops change that order.

A condition is an expression that is either true or false. C has no separate true and false for conditions: the number 0 means false, and any other number means true. The comparison operators work out to 1 for true and 0 for false. They are < (less than), <= (less than or equal), > (greater than), >= (greater than or equal), == (equal to) and != (not equal to). Take care: == compares two values, while a single = stores a value.

You combine conditions with && (and: both must be true), || (or: at least one must be true) and ! (not). Both && and || stop early. If the left side already decides the answer, the right side is not looked at. This is called short-circuit evaluation.

The statement if runs a block only when its condition is true. A block is a group of statements inside curly brackets. You can add else for the other case, and else if for more cases.

A loop repeats a block. A while loop checks its condition first and repeats as long as it is true. A for loop puts three parts in its brackets: a setup, a condition and a step.
for (int i = 0; i < 3; i++)
This means: start i at 0, keep going while i is less than 3, and after each pass add 1 to i. The expression i++ means i = i + 1. So the block runs with i equal to 0, 1 and 2, which is three times. Counting from 0 is normal in C. Each run through the block is an iteration.

A loop needs something that eventually makes its condition false. If nothing does, it never ends. Press Ctrl and C together in the terminal to stop it. The statement break leaves a loop at once. The statement continue skips to the next iteration.

Use curly brackets around every block, even when it holds one statement. It prevents a whole class of mistakes.

New words

condition
An expression used as a yes or no question. 0 means no, anything else means yes.
block
A group of statements inside curly brackets.
loop
A statement that repeats a block while a condition holds.
iteration
One run through the body of a loop.
short-circuit evaluation
&& and || skip their right side when the left side already decides the answer.

Worked example

C11
#include <stdio.h>

int main(void) {
    int total = 0;
    for (int i = 1; i <= 6; i++) {
        if (i % 2 == 0) {
            total += i;
            printf("%d is even, total is %d\n", i, total);
        } else {
            printf("%d is odd, skipped\n", i);
        }
    }

    int coins = 1;
    int days = 0;
    while (coins < 100) {
        coins = coins * 2;
        days++;
    }
    printf("coins reach %d after %d days\n", coins, days);

    int age = 17;
    if (age >= 13 && age < 20) {
        printf("age %d is a teenager\n", age);
    }
    return 0;
}
Predict the output first, then show it

Expected output

1 is odd, skipped
2 is even, total is 2
3 is odd, skipped
4 is even, total is 6
5 is odd, skipped
6 is even, total is 12
coins reach 128 after 7 days
age 17 is a teenager

Step by step

  1. The for loop sets i to 1, keeps going while i <= 6, and adds 1 to i after each pass. The block therefore runs six times.

  2. i % 2 is the remainder after dividing by 2. When it equals 0, the number is even. The condition uses == to compare.

  3. For even numbers the if branch adds i to total and prints it: 2, then 2 + 4 = 6, then 6 + 6 = 12. For odd numbers the else branch prints a message.

  4. The while loop checks coins < 100 before every pass. Each pass doubles coins and adds 1 to days. The values are 2, 4, 8, 16, 32, 64 and 128, which is seven passes. After the seventh the condition is false and the loop ends.

  5. The last if uses && to combine two comparisons. Both must be true: 17 is at least 13 and also less than 20, so the message is printed.

Common mistakes

Wrong
int x = 3;
if (x = 5) {
    printf("five\n");
}

A single = assigns. This stores 5 in x, and the value of the condition is 5, which counts as true. So the block always runs, and x is damaged. With -Wall the compiler warns: using the result of an assignment as a condition without parentheses. Use == to compare.

Right
if (x == 5) {
    printf("five\n");
}
Wrong
for (int i = 0; i < 3; i++);
{
    printf("hi\n");
}

The semicolon after the brackets ends the loop with an empty body. The loop counts to 3 and does nothing. Then the block after it runs once. The compiler warns: for loop has empty body.

Right
for (int i = 0; i < 3; i++) {
    printf("hi\n");
}
Wrong
if (1 < x < 5) {
    printf("between\n");
}

C reads this from left to right as (1 < x) < 5. The first comparison is 0 or 1, and both 0 and 1 are less than 5, so the condition is always true. Some compilers warn about it. Write two comparisons and join them with &&.

Right
if (1 < x && x < 5) {
    printf("between\n");
}
Wrong
int i = 0;
while (i < 3) {
    printf("%d\n", i);
}

Nothing changes i, so i < 3 stays true and the loop prints 0 forever. Press Ctrl and C in the terminal to stop it. Every loop needs a step that moves toward the end.

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

Check yourself

  1. Predict the output of for (int i = 1; i <= 9; i += 4) printf("%d ", i);

    Try it first, then show the answer

    1 5 9. The variable i starts at 1 and grows by 4 each time: 1, 5, 9. The next value, 13, fails the condition i <= 9.

  2. Write a loop that adds the numbers from 1 to 100 into an int called sum, then prints sum.

    Try it first, then show the answer

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

    It prints 5050. The variable sum must start at 0.

  3. Fix this condition so it tests for an even number: if (n % 2 = 0)

    Try it first, then show the answer

    Use == to compare: if (n % 2 == 0). A single = tries to assign to n % 2, and the compiler rejects it with an error such as: expression is not assignable.

05

Functions and pass by value

Goal After this you can write your own function, call it, and explain why a function cannot change the variable you passed in.

A function is a named group of instructions that you can use again and again. You have already used two: main and printf. Now you write your own.

Here is a definition:
int square(int n) { return n * n; }
It has four parts. The return type, int, is the type of the value the function hands back. The word void means it hands back nothing. The name is square. The parameters, in brackets, are the names the function uses for the values it receives, and each has a type. The body, in curly brackets, holds the instructions. The statement return sends a value back and ends the function at once.

To call a function, write its name and put values in brackets: square(5). The values you give are the arguments. The call is an expression: its value is what the function returns. So square(5) is 25, and you can print it or store it in a variable.

The compiler reads the file from top to bottom. It must learn about a function before you call it. The usual way is a prototype: the first line of the function, followed by a semicolon, placed near the top of the file. It says "this function exists, with these types, and its body comes later".

Now the key rule. When you call a function, C copies each argument into the matching parameter. This is called pass by value. The function works on its own copy. Think of giving someone a photocopy of a form. They can scribble on it, and your original stays the same. So if a function sets its parameter to 0, the variable in the caller still holds the old value. To get a new value out, return it and assign it: side = add_one(side).

Variables made inside a function, including its parameters, are local. They exist only while the function runs, and other functions cannot see them. The part of the program where a name can be used is its scope. Two functions can each have a variable called n. They are separate boxes.

Lesson 7 shows how a function can change the caller's variable on purpose.

New words

function
A named group of instructions that can be called from elsewhere.
parameter
A name in a function definition for a value the function receives.
argument
The value you give in a call. It is copied into the parameter.
return value
The value a function hands back with return.
prototype
The first line of a function plus a semicolon. It announces the function before its body.
local variable
A variable made inside a function. It exists only while the function runs.
pass by value
Calling a function copies each argument into its parameter. The original is untouched.

Worked example

C11
#include <stdio.h>

int square(int n);
void try_to_reset(int n);
int add_one(int n);

int main(void) {
    int side = 5;
    printf("square(%d) = %d\n", side, square(side));

    try_to_reset(side);
    printf("back in main, side is still %d\n", side);

    side = add_one(side);
    printf("after side = add_one(side), side is %d\n", side);
    return 0;
}

int square(int n) {
    return n * n;
}

void try_to_reset(int n) {
    n = 0;
    printf("inside try_to_reset, n is %d\n", n);
}

int add_one(int n) {
    return n + 1;
}
Predict the output first, then show it

Expected output

square(5) = 25
inside try_to_reset, n is 0
back in main, side is still 5
after side = add_one(side), side is 6

Step by step

  1. The three lines before main are prototypes. They tell the compiler that these functions exist, so main can call them even though their bodies come later.

  2. square(side) copies 5 into the parameter n. The function returns 5 * 5, which is 25, and printf prints it.

  3. try_to_reset(side) copies 5 into its own n. The function sets that n to 0 and prints 0. This changes only the copy.

  4. Back in main, side still holds 5. Pass by value protected it.

  5. add_one(side) returns 6. The assignment side = add_one(side) stores that value in side. This is how a new value gets out of a function.

  6. The bodies of the three functions come after main. The word void in try_to_reset means it returns nothing, so it has no return statement.

Common mistakes

Wrong
int main(void) {
    int n = square(3);
    printf("%d\n", n);
    return 0;
}

int square(int v) { return v * v; }

main calls square before the compiler has seen it. The compiler stops with: call to undeclared function 'square' (gcc says: implicit declaration of function 'square'). Put a prototype above main, or define square above main.

Right
int square(int v);

int main(void) {
    int n = square(3);
    printf("%d\n", n);
    return 0;
}

int square(int v) { return v * v; }
Wrong
void double_it(int n) { n = n * 2; }
/* in main: */
double_it(x);

The function doubles its own copy. The variable x in main does not change. This is the most common surprise about pass by value.

Right
int double_it(int n) { return n * 2; }
/* in main: */
x = double_it(x);
Wrong
int twice(int n) {
    n * 2;
}

The line n * 2; works out a value and throws it away. The function never returns anything. The compiler warns: expression result unused, and: non-void function does not return a value. Using the result of such a call is undefined behaviour.

Right
int twice(int n) {
    return n * 2;
}
Wrong
int helper(void) {
    int inner = 5;
    return 1;
}

int main(void) {
    helper();
    printf("%d\n", inner);
    return 0;
}

inner is local to helper. main cannot see it, and the compiler stops with: use of undeclared identifier 'inner'. To pass a value out, return it.

Right
int helper(void) {
    int inner = 5;
    return inner;
}

int main(void) {
    int got = helper();
    printf("%d\n", got);
    return 0;
}

Check yourself

  1. Predict the output: int f(int n) { n = n + 1; return n * 2; } int main(void) { int x = 4; int y = f(x); printf("%d %d\n", x, y); return 0; }

    Try it first, then show the answer

    4 10. The function adds 1 to its copy, which becomes 5, and returns 5 * 2 = 10. The variable x in main stays 4.

  2. Write a function max_of that takes two ints and returns the larger one. Show the call that prints the larger of 3 and 9.

    Try it first, then show the answer

    int max_of(int a, int b) {
    if (a > b) {
    return a;
    }
    return b;
    }

    printf("%d\n", max_of(3, 9));

    It prints 9. When the if branch returns, the function ends at once, so the second return only runs when a is not greater.

  3. This function is meant to swap two variables in the caller, but it does not. Say why. void swap(int a, int b) { int t = a; a = b; b = t; }

    Try it first, then show the answer

    The parameters a and b are copies. The function swaps the copies and then they disappear. The caller's variables are untouched. A function can return only one value, so the fix needs pointers, which lesson 7 teaches.

06

Arrays and C strings with the terminating zero

Goal After this you can keep a row of values in an array, loop over it safely, and explain why a C string needs one extra box for the terminating zero.

An array is a row of boxes of the same type, side by side in memory. The line int scores[4]; makes four int boxes. The number in the square brackets is the length.

You reach one box by its index, which is its position number. Counting starts at 0. The boxes of scores are scores[0], scores[1], scores[2] and scores[3]. The last valid index is the length minus 1. There is no scores[4]. But C does not stop you from writing it. The program reads or writes whatever memory lies there. That is undefined behaviour (lesson 3), and it causes many bugs and security holes. The compiler and the browser page will usually not warn you. Lesson 8 shows a tool that does.

You can set starting values with curly brackets: int scores[4] = {70, 85, 90, 65};

An array does not remember its own length. The expression sizeof scores gives the total bytes, which is 16 for four ints. Dividing by sizeof scores[0], which is 4, gives the length. Use that, or a named number, so the length is written in one place only.

Text is stored as an array of char. A C string is a char array in which the text is followed by a byte with the value 0, written '\0'. It is called the terminating zero, or the null character. The zero marks the end of the text. Functions like printf and strlen read characters until they meet it.

char word[] = "Leon";
The compiler makes the array five boxes long: L, e, o, n and the terminating zero. So sizeof word is 5. The function strlen, from string.h, counts the characters before the zero, so strlen(word) is 4. A string always needs one box more than its visible length. If the zero is missing, functions keep reading past the end of the array.

You can change one character: word[0] = 'N'. But you cannot copy or compare whole arrays with = or ==. To compare strings use strcmp, from string.h. It returns 0 when the two strings are equal.

Remember the difference between 'A' and "A". 'A' is one char. "A" is a string of two boxes: the letter A and the terminating zero.

New words

array
A row of boxes of the same type, side by side in memory.
index
The position number of a box in an array. The first box is index 0.
string
In C, a char array holding text followed by a terminating zero.
terminating zero
The byte with value 0, written '\0', that marks the end of a string. Also called the null character.

Worked example

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

int main(void) {
    int scores[4] = {70, 85, 90, 65};
    int length = (int)(sizeof scores / sizeof scores[0]);
    int sum = 0;
    for (int i = 0; i < length; i++) {
        sum += scores[i];
    }
    printf("length = %d, first = %d, last = %d, sum = %d\n", length, scores[0], scores[length - 1], sum);

    char word[] = "Leon";
    printf("sizeof word = %zu, strlen word = %zu\n", sizeof word, strlen(word));
    for (int i = 0; i < 5; i++) {
        printf("word[%d] = %d\n", i, word[i]);
    }

    word[0] = 'N';
    printf("%s\n", word);
    printf("equal to Neon: %d\n", strcmp(word, "Neon") == 0);
    return 0;
}
Predict the output first, then show it

Expected output

length = 4, first = 70, last = 65, sum = 310
sizeof word = 5, strlen word = 4
word[0] = 76
word[1] = 101
word[2] = 111
word[3] = 110
word[4] = 0
Neon
equal to Neon: 1

Step by step

  1. sizeof scores is 16 bytes and sizeof scores[0] is 4 bytes. Their quotient is 4, the length. The (int) in front turns the size into an int so that it fits the int variable.

  2. The loop uses i < length, not i <= length. The last index is length - 1, which is 3. The sum is 70 + 85 + 90 + 65 = 310.

  3. "Leon" has four letters, but the array word has five boxes. sizeof word is 5 and strlen(word) is 4. The difference is the terminating zero.

  4. The second loop prints all five boxes as numbers: 76, 101, 111 and 110 are the codes of L, e, o and n. The fifth box, word[4], holds 0.

  5. word[0] = 'N' replaces the first character, so %s now prints Neon. %s prints characters until it reaches the zero.

  6. strcmp(word, "Neon") returns 0 when the text is equal. The comparison == 0 turns that into 1 for true, and %d prints it.

Common mistakes

Wrong
int a[3] = {1, 2, 3};
int sum = 0;
for (int i = 0; i <= 3; i++) {
    sum += a[i];
}

The loop reaches a[3], which does not exist. The program reads a stray value from a neighbouring box and carries on. The program runs and prints a normal-looking sum, with no error. Use i < 3.

Right
for (int i = 0; i < 3; i++) {
    sum += a[i];
}
Wrong
char s[4] = "Leon";

"Leon" needs five boxes with its terminating zero, and this array has four. The zero is left out. Later, printf("%s", s) or strlen(s) runs on into whatever memory follows.

Right
char s[5] = "Leon";
/* or let the compiler count: */
char t[] = "Leon";
Wrong
if (name == "Leon") {
    printf("hello\n");
}

== compares where the two texts are stored, not what they say. The compiler warns: result of comparison against a string literal is unspecified. Use strcmp, and include string.h.

Right
if (strcmp(name, "Leon") == 0) {
    printf("hello\n");
}
Wrong
char name[] = "Leon";
name = "Mia";

An array cannot be assigned as a whole. The compiler stops with: array type 'char[5]' is not assignable. Copy the characters with strcpy, from string.h, but only when the new text plus its zero fits.

Right
strcpy(name, "Mia");  /* "Mia" and its zero need 4 boxes; name has 5 */

Check yourself

  1. Predict the output of char t[] = "Hi"; printf("%zu %zu\n", sizeof t, strlen(t));

    Try it first, then show the answer

    3 2. The array holds H, i and the terminating zero, so sizeof is 3. strlen counts only the two letters.

  2. Write a loop that finds and prints the largest value in int v[4] = {4, 9, 2, 7};

    Try it first, then show the answer

    int v[4] = {4, 9, 2, 7};
    int largest = v[0];
    for (int i = 1; i < 4; i++) {
    if (v[i] > largest) {
    largest = v[i];
    }
    }
    printf("%d\n", largest);

    It prints 9. Starting from v[0] avoids a made-up starting value.

  3. Write the declaration of a char array that holds the string Mia, and say how many boxes it needs.

    Try it first, then show the answer

    char name[] = "Mia"; It needs 4 boxes: M, i, a and the terminating zero. Writing char name[4] = "Mia"; also works, but char name[3] would leave out the zero.

07

Memory as numbered boxes: address and pointer

Goal After this you can draw memory as numbered boxes, say what an address and a pointer are, and use & and * to read and change a variable through a pointer.

First in words. Picture the computer's memory as one very long street of small boxes. Each box holds one byte. Every box has a number painted on its door, counted from the start of the street. That number is the box's address.

A variable lives in one box or in several boxes in a row. An int usually needs four boxes side by side, so one int covers four addresses, and its address is the number of its first box. An array of three ints is twelve boxes in a row.

Now the new idea. An address is only a number, so it can be stored in a variable too. A variable that holds an address is a pointer. A pointer is like a card with a house number on it. The card is not the house. But from the number you can walk to the house, look inside, or change what is inside. Following the card is called dereferencing.

A small drawing, with made-up numbers. The variable age sits at address 100 and holds 20. The variable p sits at address 200 and holds the number 100. We say p points to age. The example below draws the same picture in a comment.

Now with C symbols. Two symbols do the work.
The expression &x means "the address of x".
The expression *p means "the thing that p points to".

The line int *p = &age; declares p as a pointer to an int. In a declaration, read the star as "pointer to". It also gives p the address of age. In an expression, the star means "follow the pointer". After that line, *p and age are the same box. Reading *p gives 20. Writing *p = 21 changes age.

This solves the problem from lesson 5. A function gets copies, but if the copy is an address, the function can follow it to the caller's variable. The call add_one(&age) passes the address of age. Inside, *p = *p + 1 changes the caller's age. The function scanf works this way too. In scanf("%d", &n) the & gives scanf the address of n, so scanf can store the number there.

Pointers and arrays are close relatives. The name of an array stands for the address of its first box. If q points at nums[0], then q + 1 points at nums[1]. Adding 1 to a pointer moves it forward by one whole int, which is 4 bytes, not by one byte. The expressions *(q + 1) and q[1] mean the same thing.

A pointer that points at nothing holds NULL. Never follow a NULL pointer, or a pointer that was never set. The program crashes with a segmentation fault, or it silently damages data.

If you add printf("%p\n", (void *)p); you will see a real address, such as 0x16d8a3a5c. Yours will be different, and it can change on every run.

New words

address
The number of a box in memory. A variable's address is the number of its first box.
pointer
A variable that holds an address.
&x
"The address of x".
*p
"The thing p points to". Following the pointer is called dereferencing.
NULL
The value of a pointer that points at nothing. Never dereference it.

Worked example

C11
#include <stdio.h>

/* Memory drawn as numbered boxes (the numbers are made up):

     address   contents      name
     -------   -----------   ----
       100     20            age
       200     100           p      <- p holds the address of age
*/

void add_one(int *p) {
    *p = *p + 1;
}

int main(void) {
    int age = 20;
    int *p = &age;

    printf("age = %d\n", age);
    printf("*p = %d\n", *p);
    printf("p points at age: %d\n", p == &age);

    *p = 21;
    printf("after *p = 21, age = %d\n", age);

    add_one(&age);
    printf("after add_one(&age), age = %d\n", age);

    int nums[3] = {10, 20, 30};
    int *q = nums;
    printf("*q = %d, *(q + 1) = %d, q[2] = %d\n", *q, *(q + 1), q[2]);
    printf("bytes between nums[0] and nums[1]: %d\n", (int)((char *)&nums[1] - (char *)&nums[0]));

    int *nothing = NULL;
    if (nothing == NULL) {
        printf("nothing points at no object\n");
    }
    return 0;
}
Predict the output first, then show it

Expected output

age = 20
*p = 20
p points at age: 1
after *p = 21, age = 21
after add_one(&age), age = 22
*q = 10, *(q + 1) = 20, q[2] = 30
bytes between nums[0] and nums[1]: 4
nothing points at no object

Step by step

  1. The comment is the picture. age sits at made-up address 100 and holds 20. p sits at address 200 and holds 100, the address of age.

  2. int *p = &age; makes p a pointer to int and stores the address of age in it. Printing *p follows the pointer and gives 20, the same as age.

  3. p == &age compares two addresses. They are the same, so the result is 1 for true.

  4. *p = 21 writes through the pointer. The box named age now holds 21, though the name age was not used.

  5. add_one(&age) hands the function the address. The function adds 1 to the box it points at, so age becomes 22. This is what lesson 5 could not do.

  6. q = nums makes q point at nums[0]. *q is 10, *(q + 1) is 20 and q[2] is 30. The two boxes nums[0] and nums[1] are 4 bytes apart, because an int takes 4 bytes. The (char *) casts make the subtraction count bytes.

  7. nothing holds NULL. The program compares it with NULL but never follows it.

Common mistakes

Wrong
int *p;
*p = 5;

p was never set, so it holds leftover bits that point nowhere in particular. Writing through it crashes with a segmentation fault, or silently damages some other variable. With -Wall, clang warns: variable 'p' is uninitialized when used here.

Right
int x = 0;
int *p = &x;
*p = 5;
Wrong
int x = 4;
int *p = &x;
p = 7;

Without the star, this stores the number 7 in the pointer itself, as if 7 were an address. The compiler complains: incompatible integer to pointer conversion assigning to 'int *' from 'int'. To change the thing p points to, put the star in front.

Right
*p = 7;
Wrong
int *make(void) {
    int local = 5;
    return &local;
}

local is a box inside make. When make ends, the box is given up, and the address now points at a place that no longer belongs to you. The compiler warns: address of stack memory associated with local variable 'local' returned. Return the value instead.

Right
int make(void) {
    return 5;
}
Wrong
int *p = NULL;
*p = 5;

NULL points at no object. Writing through it stops the program with Segmentation fault. Check the pointer before you follow it.

Right
if (p != NULL) {
    *p = 5;
}

Check yourself

  1. Predict the output of int x = 5; int *p = &x; *p = *p * 2; printf("%d\n", x);

    Try it first, then show the answer

    10. *p is the same box as x. The statement doubles the value in that box, so x holds 10.

  2. Write a function swap that really swaps two ints in the caller, and the call that swaps the variables x and y.

    Try it first, then show the answer

    void swap(int *a, int *b) {
    int t = *a;
    *a = *b;
    *b = t;
    }

    swap(&x, &y);

    The function receives the two addresses and works on the boxes they point at, so the caller's x and y change.

  3. Predict the output of int nums[3] = {10, 20, 30}; int *q = nums; q = q + 2; printf("%d\n", *q);

    Try it first, then show the answer

    30. Adding 2 moves q forward two ints, from nums[0] to nums[2].

08

Reading compiler messages and using -Wall

Goal After this you can read the parts of a warning or an error, fix the first error first, and turn on the compiler's checks so mistakes show up before the program runs.

The compiler talks to you in messages. They are the fastest teacher you have, if you can read them. A message looks like this:
hello.c:5:20: warning: format specifies type 'int' but the argument has type 'double' [-Wformat]

Read it from left to right. hello.c is the file. 5 is the line number. 20 is the column, the position of the character in that line. The word warning is the kind of message. The sentence says what the compiler thinks is wrong. The name in square brackets, -Wformat, is the check that fired, and you can search for it. Below the message the compiler usually shows your line, with a ^ under the spot. A line starting with note: adds detail to the message above it.

There are three kinds. An error means the compiler could not build the program, so no executable is made. A warning means the program builds but something looks wrong. Treat every warning as a bug until you know better. A note adds detail.

The messages in this guide come from clang, the compiler on a Mac. gcc, on Linux and in WSL, words them a little differently. For the example above it says: format '%d' expects argument of type 'int', but argument 2 has type 'double'. The file, line, kind, sentence and check name are all there.

By default a compiler stays quiet about many risky things. You ask for more with options, which you write after cc.
-Wall turns on the common warnings. Despite its name it does not turn on every warning.
-Wextra turns on more.
-Werror makes every warning an error, so you cannot ignore any.
-std=c11 chooses the C11 version of the language.
-g keeps line information so that tools can show your line numbers.
-fsanitize=address,undefined builds a program that checks itself while it runs. It stops at the first bad memory access or undefined operation and names the line.

A good everyday command is:
cc -std=c11 -Wall -Wextra -g -fsanitize=address,undefined main.c -o main

Some habits help. Fix the first message first: one mistake can cause a chain of later messages, so compile again after each fix. Remember that the line number is where the compiler noticed the problem, and the cause can be on the line above. Never ignore a warning you do not understand.

The sanitizer matters because the Run button on this page cannot do this job. If a program reads past the end of an array, it runs on this page and prints a normal-looking answer. Nothing checks. Built with the sanitizer, the same program stops with a report that starts with ERROR: AddressSanitizer or runtime error and names the line. That is why a real compiler is worth the ten minutes of setup.

You can now read the C in the first chapters of the course: types and sizes, conditions and loops, functions that receive copies, arrays and strings with their zero, and pointers. The course goes deeper into each of them. It also adds structures, files, allocating memory with malloc, and much more.

New words

error
A message that means the compiler could not build the program.
warning
A message that means the program builds but something looks wrong.
note
A line starting with note: that adds detail to the message above it.
option (flag)
Extra text after cc that changes what the compiler does, such as -Wall.
sanitizer
A check built into the program. It stops the program at the first bad memory access or undefined operation and names the line.

Worked example

C11
#include <stdio.h>

int main(void) {
    int scores[3] = {80, 90, 85};
    int sum = 0;
    for (int i = 0; i < 3; i++) {
        sum += scores[i];
    }
    double average = (double)sum / 3;
    printf("sum = %d, average = %.1f\n", sum, average);
    return 0;
}
Predict the output first, then show it

Expected output

sum = 255, average = 85.0

Step by step

  1. Compile it with the common checks: cc -std=c11 -Wall -Wextra average.c -o average. If the compiler prints nothing, it found nothing to complain about. Then run ./average.

  2. (double)sum / 3 is a cast. The (double) turns sum into a double for this one calculation, so the division keeps the fraction. Without it, 255 / 3 would be an integer division.

  3. Now break it on purpose, one change at a time, and compile again. First, delete the semicolon after sum += scores[i]. Clang says: error: expected ';' after expression. gcc says: expected ';' before '}' token. Put the semicolon back.

  4. Second, change %.1f to %d in the printf. Clang says: warning: format specifies type 'int' but the argument has type 'double' [-Wformat]. Change it back.

  5. Third, delete the whole line int sum = 0; Clang says: error: use of undeclared identifier 'sum'. gcc says: 'sum' undeclared (first use in this function). The error is reported at every use of sum, and the one missing line causes them all. Put the line back.

Common mistakes

Wrong
#include <stdio.h>
int main(void) {
    itn count = 0;
    count = count + 1;
    printf("%d\n", count);
    return 0;
}

$ cc demo.c -o demo
demo.c:3:5: error: use of undeclared identifier 'itn'
demo.c:4:5: error: use of undeclared identifier 'count'
demo.c:4:13: error: use of undeclared identifier 'count'
demo.c:5:20: error: use of undeclared identifier 'count'
4 errors generated.

One typo, itn instead of int, produced four errors. The compiler does not know what count is, because the line that declares it failed. Fix the first error and compile again, and the other three disappear. Do not fix errors from the bottom up. (gcc words these errors differently and may print fewer of them.)

Right
int count = 0;
Wrong
#include <stdio.h>
int main(void) {
    int total = 3
    printf("%d\n", total);
    return 0;
}

The semicolon is missing at the end of line 3. Some compilers, gcc among them, can name line 4, because that is where they noticed the statement had not ended. The cause is then on the line above the one named. When the named line looks right, check the line before it.

Right
int total = 3;
Wrong
#include <stdio.h>
int main(void) {
    int total;
    total = total + 5;
    printf("%d\n", total);
    return 0;
}

$ cc demo.c -o demo
(no output: it builds)

With no options, the compiler says nothing about the unset variable total, so the program builds and gives an unpredictable answer. Ask for the checks every time, and read each warning.

Right
$ cc -std=c11 -Wall -Wextra demo.c -o demo
demo.c:4:13: warning: variable 'total' is uninitialized when used here [-Wuninitialized]
Wrong
#include <stdio.h>
int main(void) {
    int a[3] = {1, 2, 3};
    int sum = 0;
    for (int i = 0; i <= 3; i++) {
        sum += a[i];
    }
    printf("%d\n", sum);
    return 0;
}

(Run button on this page, or a plain cc build: prints a normal-looking sum)

The loop reads a[3], which is outside the array. Nothing in the browser, and nothing in an ordinary build, checks this. Built with -g -fsanitize=address,undefined, the program stops and reports runtime error: index 3 out of bounds, and also ERROR: AddressSanitizer: stack-buffer-overflow, naming the line. The Run button on this page cannot do that. This is why you should install a real compiler.

Right
$ cc -std=c11 -Wall -Wextra -g -fsanitize=address,undefined demo.c -o demo
$ ./demo
demo.c:6:16: runtime error: index 3 out of bounds for type 'int[3]'

Check yourself

  1. Read this message and name the file, the line, the kind and the check: demo.c:12:9: warning: unused variable 'count' [-Wunused-variable]

    Try it first, then show the answer

    The file is demo.c, the line is 12 (column 9), the kind is a warning, and the check is -Wunused-variable. It means a variable called count is declared but never used. Delete it or use it.

  2. The compiler reports error: expected ';' after expression at line 8, but line 8 looks correct. Where do you look next, and why?

    Try it first, then show the answer

    Look at line 7, the line before. The compiler reports the place where it noticed the statement had not ended, and a missing semicolon is usually at the end of the line above.

  3. Write the command that builds main.c into a program called main, using C11, the common warnings and the sanitizers.

    Try it first, then show the answer

    cc -std=c11 -Wall -Wextra -g -fsanitize=address,undefined main.c -o main
    The name after -o is the program to create. Never put main.c there.

What next

Continue with the COMP2017 course notes, starting at chapter 1 →

Independent study notes, not official course material.