Compiling and linking

Table of contents
  1. Compiling and linking
    1. What are compiling and linking?
    2. Building everything in one command
    3. Compiling source files separately
    4. Linking the object files
    5. Why compile files separately?
    6. Compiler errors vs linker errors
      1. Compiler errors
      2. Linker errors
    7. The complete build process

A project containing several .c files must be compiled and linked before it can run.

Our example project contains:

very_cool_project/
├── main.c
├── very_cool_maths.c
└── very_cool_maths.h

The build process has two main stages:

.c source files
      |
      | compile
      v
.o object files
      |
      | link
      v
executable program

What are compiling and linking?

Building a C program has two main stages:

  • Compiling translates each .c file into machine code stored in an object file:
    main.c -> main.o
  • Linking combines the object files into one executable program:
    main.o + very_cool_maths.o -> very_cool_project

A useful way to remember the difference is:

    Compile: translate each source file into machine code
    Link:    combine the compiled files into one program

Building everything in one command

For a small project, we can compile all the source files using one command:

gcc main.c very_cool_maths.c -o very_cool_project.out

The source files are:

main.c
very_cool_maths.c

The -o option sets the name of the executable:

very_cool_project

We do not include the header file in the command. The source files include the headers they need:

#include "very_cool_maths.h"

On the Linux Codespace, run the finished program using:

./very_cool_project

Compiling source files separately

We can also compile each source file separately using -c:

gcc -c main.c
gcc -c very_cool_maths.c

The -c option tells GCC to compile the source file without trying to create the final executable.

This produces two object files:

main.o
very_cool_maths.o

An object file contains compiled machine code, but it is not yet a complete program that we can run.

Linking the object files

The object files are combined by the linker:

gcc main.o very_cool_maths.o -o very_cool_project

This produces the executable:

very_cool_project

The complete sequence is therefore:

gcc -c main.c
gcc -c very_cool_maths.c
gcc main.o very_cool_maths.o -o very_cool_project

The first two commands compile the source files. The final command links the object files.

Why compile files separately?

This seems like an unnecessary complication! Why bother doing this? Well, suppose we change only:

very_cool_maths.c

We do not need to compile main.c again. We can rebuild only the file that changed:

gcc -c very_cool_maths.c

We then link the new object file with the existing main.o:

gcc main.o very_cool_maths.o -o very_cool_project

This does not make much difference for our very cool but very small project. It becomes useful when a project contains many source files and takes longer to compile, like your projects in Unit 3 & 4.

Makefiles automate these commands and work out which files need to be rebuilt - very handy!

Compiler errors vs linker errors

The type of error helps tell us where in the process of creating the executable the problem occurred.

Compiler errors

A compiler error means that one of the source files could not be compiled.

This is normally caused by a problem in the C code, like missing semicolons, undeclared variables or incorrect syntax.

For example:

int add_exciting_numbers(int first, int second)
{
    return first + second
}

This is missing a semicolon, so very_cool_maths.c cannot be compiled into very_cool_maths.o.

Linker errors

A linker error happens after the source files have compiled, but the object files cannot be combined into the final program.

A common example is:

undefined reference to `add_exciting_numbers'

This means the compiler saw the function declaration, but the linker could not find its definition.

For example, we may have forgotten to include very_cool_maths.c in the build:

gcc main.c -o very_cool_project

The correct command is:

gcc main.c very_cool_maths.c -o very_cool_project

A linker can also report that a function has been defined more than once. This often happens when a .c file has been included instead of its header, or you forgot header guards.

A useful way to remember the difference is:

Compiler error: one of the source files could not be compiled
Linker error: the files compiled, but could not be combined into a program

If you see an undefined reference, start by checking that the relevant .c file is included in the build.

The complete build process

For our example project:

main.c ──────────────────► main.o
                                  \
                                   ► very_cool_project
                                  /
very_cool_maths.c ───────► very_cool_maths.o

The header file is included while each source file is compiled:

very_cool_maths.h
        |
        ├── included by main.c
        |
        └── included by very_cool_maths.c

The header provides declarations. It does not produce its own object file.

For now, the commands to remember are:

gcc -c main.c
gcc -c very_cool_maths.c
gcc main.o very_cool_maths.o -o very_cool_project

In practice, we do not want to type all of these commands whenever something changes. Later in this section, we will put the build instructions into a Makefile.


This site uses Just the Docs, a documentation theme for Jekyll.