Seminar
: Compilation and makefiles
Compiling in C++
Compilation in C++ is performed by a compiler. If you do not remember what compilers do, go back to the previous module, Topic 4 of this course (the Introduction to C++).
If you followed the instructions on the welcome page you should have a compiler installed.
This is g++
and it is a command-line utility with various options, some of which depend on your specific machine.
When your code is contained in a single source file, e.g. main.cpp
compilation is trivial
g++ main.cpp -o myprogram
This produces an executable file named myprogram
that you can run via
./myprogram
Compilation flags
The g++
compiler had various compilation flags that go under the following syntax
g++ [options] source_files -o output_file
Common flags include:
- Optimization:
-O0
: No optimization (default).
-O1
,-O2
,-O3
,-Ofast
: Increasing levels of optimization.
- Debugging:
-g
: Include debugging information for use with a debugger likegdb
.
- Warnings:
-Wall
: Enable common warnings.
-Werror
: Treat warnings as errors.
- Standards Compliance:
-std=c++11
,-std=c++14
,-std=c++17
, etc.: Specify the C++ standard to follow.
- Linking:
-L<path>
: Specify a directory for library search paths.
-l<library>
: Link a specific library (e.g.,-lm
for the math library).
- Output:
-o <filename>
: Specify the output file name.
- Preprocessor:
-D<macro>
: Define a preprocessor macro.
-I<path>
: Add include directory for header files.
- Performance:
-fopenmp
: Enable OpenMP for parallel programming.
For example, the following compiles two files main.cpp
and utils.cpp
to produce a single program program
with optimisation level 2
, with warnings enabled and using the 2017 standard for C++.
g++ -std=c++17 -O2 -Wall -o program main.cpp utils.cpp
Notice that this in fact performs two actions at the same time:
- compiles the source code into objects, equivalent to
g++ -std=c++17 -O2 -Wall -c main.cpp -o main.o
g++ -std=c++17 -O2 -Wall -c utils.cpp -o utils.o
- links the object into an executable
g++ -std=c++17 -O2 -Wall main.o utils.o -o program
Makefiles
When a project becomes complex (with many files and eventually various kinds of flags) typing a long string just for compilation makes little sense. Ideally, we want to automatise this step, by writing a dedicated script that does it for us.
We could write a separate bash
script to do so, but it would not be very efficient.
The standard way is to construct a Makefile
and to use it with the normally available command-line utility make
. Makefiles are a special type of file with a characteristic syntax.
Often, Makefiles are quite complex, or even automatically generated. Here, we write a simple, interpretable makefile from scratch.
Creating a Makefile
We want to transpose the compilation commands into a makefile. So, in the source folder (i.e. wherever the code is stored) we create a file called Makefile
.
touch Makefile
Structure of a Makefile
A Makefile consists of
- Variables: these identify compilers, file names, and flags.
- Rules: these are actions that the Makefile can perform, each with a name associated with it. Each rule consists of a target, dependencies, and a recipe (command).
Variables
The obvious variables for a Makefile are:
- the compiler name. Typically this variable is called
CC
. - the compiler flags. Typically this variable is called
CFLAGS
. - the source files, i.e. the source code for our program. This is normally called
SRC
. - the executable name, e.g.
EXEC
.
Note that typically these names are written in uppercase letters. In the Makefile syntax, we access the value stored in these variables using the $(VARIABLE)
syntax.
Rules
Rules in a Makefile describe how to build a target from its dependencies using commands. A rule typically follows this format:
target: dependencies
commands
target
: The file or outcome to create (e.g., an object file.o
or the final executable).dependencies
: The files or other targets required to build the target.commands
: The shell commands to execute, usually for compiling or linking.
Minimal makefile
Suppose that we want to write a rule to compile our project. Our target would be $(EXEC)
(the name of our executable) our dependencies would be the source code $(SRC)
and the command would simply combine the compiler, its flags, the source code and the executable name, so that the rule would look like
$(EXEC): $(SRC)
$(CC) $(CFLAGS) $(SRC) -o $(EXEC)
A complete minimal Makefile would look like (remember to use true tabs for the indentation)
CC = g++
CFLAGS = -O2 -Wall
SRC = main.cpp utils.cpp
EXEC = program
# check that the indentation is a tab and not spaces
$(EXEC): $(SRC)
$(CC) $(CFLAGS) $(SRC) -o $(EXEC)
Running a makefile
How would we run this Makefile? You should type make
and the name of the rule that you want to call, in this case program
as in
make program
What if we just want to have a default rule to run every time we type make (after all, we want to simplify our lives)? There is a special target named all
. We can then rewrite our minimal Makefile as
CC = g++
CFLAGS = -O2 -Wall
SRC = main.cpp utils.cpp
EXEC = program
# check that the indentation is a tab and not spaces
all:
$(CC) $(CFLAGS) $(SRC) -o $(EXEC)
Keeping all as the first rule in the Makefile makes it default.
Another useful rule is called clean
: you can implement it to remove files, as to remove the executable safely.
clean:
rm -f $(EXEC)