Seminar
: Compilation and makefilesCompilation 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
This produces an executable file named myprogram
that you can run via
The g++
compiler had various compilation flags that go under the following syntax
Common flags include:
-O0
: No optimization (default).-O1
, -O2
, -O3
,-Ofast
: Increasing levels of optimization.-g
: Include debugging information for use with a debugger like gdb
.-Wall
: Enable common warnings.-Werror
: Treat warnings as errors.-std=c++11
, -std=c++14
, -std=c++17
, etc.: Specify the C++ standard to follow.-L<path>
: Specify a directory for library search paths.-l<library>
: Link a specific library (e.g., -lm
for the math library).-o <filename>
: Specify the output file name.-D<macro>
: Define a preprocessor macro.-I<path>
: Add include directory for header files.-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++.
Notice that this in fact performs two actions at the same time:
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.
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
.
A Makefile consists of
The obvious variables for a Makefile are:
CC
.CFLAGS
.SRC
.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 in a Makefile describe how to build a target from its dependencies using commands. A rule typically follows this format:
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.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
A complete minimal Makefile would look like (remember to use true tabs for the indentation)
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
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.
Task 1: writing a minimal Makefile
Using the information above, download the following project folder and write a minimal Makefile that can compile the following project:
Run your makefile using the make
command.
Task 2: adding compilation flags
Modify your Makefile to use the -std=c++17
compilation flag.
Task 3: separating compilation and linking
The Makefile you have written is minimal and blends together compiling and linking. However, it is a better practice to separate the two, since very large projects can have many files and modifying only a few of the object files instead of recreating all of them is much more efficient.
The Make syntax has a convenient set of features that allow you to automatically produce a list of object files. For example
creates a variable called OBJ
that contains all the .o
object files such as main.o
produced by g++ -c main.cpp -o main.o
.
Do the following:
OBJ = $(SRC:.cpp=.o)
to define your object files. You can print in makefiles using the command @echo
, so that @echo $(OBJ)
should print to terminal the values stored in OBJ
. Use the fact that rules can be multiline to modify your Makefile and print your object variable.which tells make
to compile each .cpp file ($<)
into the corresponding .o file ($@
). Add this rule to your makefile.
$(EXEC)
, your dependencies are now the object files and the rule is just the linking step described above.all
rule to depend on $(EXEC)
and to write the message "Build complete"
. The all
rule should do nothing but printing messages.