Step 11 · Ubuntu · Independent

Compiler options that find bugs

In step 3 you compiled with clang -g hello.c -o hello. Adding a few more options turns the compiler from something that merely translates your code into something that actively hunts for mistakes in it.

This step needs clang from step 3

Warnings are just options you pass — nothing to install. The sanitizer is different: Ubuntu keeps its runtime library in a separate package, and without it clang compiles your program and then fails to link it. This step installs that package.

Run this

$ curl -fsSL https://aikaryashala.com/system_setup/scripts/install_sanitizers.sh | bash

It writes overflow.c into ~/c-samples — the program used below — and checks both warnings and the sanitizer actually work on your machine.

Let the compiler warn you

Most C bugs are things the compiler already had doubts about. It stays quiet unless you ask. This program uses a variable it never gave a value to:

#include <stdio.h>

int main(void)
{
    int counted;                  /* never given a value */
    printf("%d\n", counted);
    return 0;
}

Compiled the step 3 way, clang says nothing at all:

$ clang -g warn.c -o warn

Ask for warnings and it tells you exactly what is wrong, and where:

$ clang -g -Wall -Wextra warn.c -o warn
warn.c:6:20: warning: variable 'counted' is uninitialized when used here [-Wuninitialized]
    6 |     printf("%d\n", counted);
      |                    ^~~~~~~
OptionWhat it does
-WallTurn on the common warnings. Despite the name, this is not all of them.
-WextraTurn on a further set that catches subtler mistakes.
-WerrorTreat every warning as an error, so the program refuses to build until it is clean.
Make this your normal compile line

clang -g -Wall -Wextra yourfile.c -o yourprogram. It costs nothing, and the compiler will catch mistakes that would otherwise take you an afternoon to find by hand.

A bug warnings cannot catch

Some mistakes only exist while the program is running. This one writes to the fifth slot of a four-slot array:

#include <stdio.h>

int main(void)
{
    int numbers[4] = {1, 2, 3, 4};

    numbers[4] = 99;              /* one past the end of the array */

    printf("%d\n", numbers[0]);
    return 0;
}

Build and run it the ordinary way:

$ cd ~/c-samples
$ clang -g overflow.c -o overflow
$ ./overflow
1

It printed 1 and exited cleanly. No crash, no complaint — and that is precisely the danger. The program wrote 99 into memory that did not belong to it and carried on as if nothing happened. On another day, with slightly different code, that stray write lands on something that matters and the program fails somewhere else entirely, for no visible reason.

Turn on AddressSanitizer

-fsanitize=address asks clang to build in checks around every memory access. The program runs a little slower, and tells you the instant something goes wrong:

$ clang -g -fsanitize=address overflow.c -o overflow
$ ./overflow
==1234==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffd...
WRITE of size 4 at 0x7ffd... thread T0
    #0 0x... in main overflow.c:7:16

Address ... is located in stack of thread T0 at offset 48 in frame
    #0 0x... in main overflow.c:4

  This frame has 1 object(s):
    [32, 48) 'numbers' (line 5) <== Memory access at offset 48 overflows this variable

Read that from the top and it tells you everything:

A habit worth forming

While you are learning, compile with clang -g -Wall -Wextra -fsanitize=address. Between them, warnings catch what is visible in the source and the sanitizer catches what only appears at run time. Turn the sanitizer off when you want the program to run at full speed.

Optimisation, and why to leave it alone for now

clang can rewrite your code to make it faster. That is what the -O options control:

OptionMeaning
-O0No optimisation. The default, and what you want while debugging.
-O1 -O2Progressively more rewriting, for speed.
-O3More still, sometimes making the program larger.
-OsOptimise for a small program rather than a fast one.

You do not need to pass -O0: clang already does no optimisation unless asked. It is worth knowing the name, because optimised code reorders and deletes things, so stepping through it in lldb jumps around confusingly and some variables simply are not there any more. Debug at -O0; optimise when the program is correct.

Check it worked

$ cd ~/c-samples && clang -g -fsanitize=address overflow.c -o overflow && ./overflow

You should see the AddressSanitizer report above, not the number 1. If you see 1, the sanitizer did not get built in — check the command for a typo.