Compilation

Fundamentals on Computing for Robotics, Graphics and Computer Vision

Darío Suárez - Adolfo Muñoz

#include

Copy and paste a file (usually, a .h header) into a source code file.

#include

Looks for the .h file relative to this source file:


        #include "file.h"
        

Looks for the .h file in the system's include paths

These files become this
factorial.h

                    int factorial(int n) {
                        if (n>1) 
                            return n*
                             factorial(n-1);    
                        else 
                            return 1;
                    } 
                    
main.cc

                    #include "factorial.h"
                    int main() {
                        ...
                    }  
                    

                    int factorial(int n) {
                        if (n>1) 
                            return n*
                             factorial(n-1);    
                        else 
                            return 1;
                    }
                    int main() {
                        ...
                    }
                    

What happens now?

factorial.h

                    int factorial(int n) {
                        if (n>1) 
                            return n*
                             factorial(n-1);    
                        else 
                            return 1;
                    } 
                    
comb.h

                    #include "factorial.h"
                    int comb(int k, int k) {
                        return factorial(n)/
                          (factorial(k)* 
                           factorial(n-k));
                    }  
                    
main.cc

                    #include "factorial.h"
                    #include "comb.h"
                    
                    int main() {
                        ...
                    } 
                    

factorial appears twice. Compilation error.

factorial.h

                    #ifndef _FACTORIAL_H_
                    #define _FACTORIAL_H_   
                    int factorial(int n) {
                        if (n>1) 
                            return n*
                             factorial(n-1);    
                        else 
                            return 1;
                    }
                    #endif  
                    
comb.h

                    #include "factorial.h"
                    int comb(int k, int k) {
                        return factorial(n)/
                          (factorial(k)* 
                           factorial(n-k));
                    }  
                    
main.cc

                    #include "factorial.h"
                    #include "comb.h"
                    
                    int main() {
                        ...
                    } 
                    

Prevent double inclusion

Include guards

In C++, all header files should be protected by guards.

It is too easy to mess it up

factorial.h

                    #ifndef _FACTORIAL_H_
                    #define _FACTORIAL_H_   
                    int factorial(int n);
                    #endif
                
comb.h

                    #ifndef _COMB_H_
                    #define _COMB_H_   
                    int comb(int n, int k);
                    #endif
                

Pragma guards

In C++, all header files should be protected by guards.

factorial.h

                    #pragma once   
                    int factorial(int n);
                
comb.h

                    #pragma once   
                    int comb(int n, int k);
                

Compile

Convert source code into a machine-code or lower-level form in which the program can be executed

Compilation flags

  • -O0 - No optimization, faster compile time, better for debugging
  • -O2 - Default optimization
  • -O3 - Higher optimization, slower compile time, better for production
  • -g - Include debugging symbols
  • -s - Remove all debugging information

Compilation flags

For debugging:


            g++ -O0 -g file.cpp -o executable
        

For production (release):


            g++ -O3 -s file.cpp -o executable
        

Compilation flags

  • -Wall - Turns on many warnings
  • -Wextra - Enables extra warnings
  • -Wpedantic - Even more warnings
  • -Werror - Transforms warnings into errors

Compilation flags

Use all warning flags! It is better to detect errors during compilation than during execution.

For debugging:


                g++ -Wall -Wextra -Wpedantic -Werror -O0 -g file.cpp -o executable
            

For production (release):


                g++ -Wall -Wextra -Wpedantic -Werror -O3 -s file.cpp -o executable
            

Single file

.cpp .exe Compile

            g++ $FLAGS file.cpp -o executable
        

Multiple files

.cpp .exe Compile .cpp .cpp

            g++ $FLAGS file1.cpp file2.cpp ... -o executable
        

Multiple files on demand

.cpp .o .cpp .o .cpp .o .exe Link Compile

            g++ $FLAGS -c file1.cpp 
            g++ $FLAGS -c file2.cpp
            ...
            g++ $FLAGS file1.o file2.o ... -o executable
        

Multiple files on demand


            g++ $FLAGS -c file1.cpp 
            g++ $FLAGS -c file2.cpp
            ...
            g++ $FLAGS file1.o file2.o ... -o executable
        
  • Manual control of compilation
  • Requires knowledge about .h and .cpp edition
  • Automating this: enter make

Make

make is a build system: it drives the compiler and other tools to build your code.

Make

It is useful when builds require multiple files, as it keeps track of their updates

.cpp .o .cpp .o .cpp .o .exe Link Compile

Make

Configurable through a Makefile. Based on a set of rules that are executed if files have changed.


        file1.o: file1.cpp file1.h
            g++ -c file1.cpp
        file2.o: file2.cpp file2.h
            g++ -c file2.cpp
        file3.o: file3.cpp file3.h
            g++ -c file3.cpp
            
        executable: file1.o file2.o file3.o
            g++ file1.o file2.o file3.o -o executable
        

Make

Configurable through a Makefile. Based on a set of rules that are executed if files have changed.


        CXX = g++
        CXXFLAGS = -Wall -Wextra -Wpedantic -Werror -O0 -g

        file1.o: file1.cpp file1.h
            $(CXX) $(CXXFLAGS) -c file1.cpp
        file2.o: file2.cpp file2.h
            $(CXX) $(CXXFLAGS) -c file2.cpp
        file3.o: file3.cpp file3.h
            $(CXX) $(CXXFLAGS) -c file3.cpp

        executable: file1.o file2.o file3.o
            $(CXX) $(CXXFLAGS) file1.o file2.o file3.o -o executable
        

Make

Configurable through a Makefile. Based on a set of rules that are executed if files have changed.


        objects = file1.o file2.o file3.o
        CXX = g++
        CXXFLAGS = -Wall -Wextra -Wpedantic -Werror -O0 -g

        $(objects): %.o: %.cpp %.h
            $(CXX) $(CXXFLAGS) -c $^

        executable: $(objects)
            $(CXX) $(CXXFLAGS) $(objects) -o executable
        

Make

Configurable through a Makefile. Based on a set of rules that are executed if files have changed.

  • Incompatible with other build systems or IDEs
  • Heavily dependant on shell and operating system
  • No cross plattform

CMake

cmake is a build system generator: it can produce Ninja build files, Visual Studio projects, Makefiles...

It is based on a configuration file named CMakeLists.txt

CMake

CMakeLists.txt are cross-plattform. Then you can generate a particular build system for your particular plattform. CMakeListstxt should be in the root folder of the project

CMakeLists.txt


        cmake_minimum_required(VERSION 3.15...3.30)
        project(
            MyCMakeProject
            VERSION 1.0
            LANGUAGES CXX
        )

        add_executable(executable file1.cpp file2.cpp file3.cpp)
        #You can have as many executables as possible (maybe for testing)  
        

CMakeLists.txt


        cmake_minimum_required(VERSION 3.15...3.30)
        project(
            MyCMakeProject
            VERSION 1.0
            LANGUAGES CXX
        )

        #For all executables in project 
        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic -Werror")

        add_executable(executable file1.cpp file2.cpp file3.cpp)
        #You can have as many executables as possible (maybe for testing)  
        

CMakeLists.txt


        cmake_minimum_required(VERSION 3.15...3.30)
        project(
            MyCMakeProject
            VERSION 1.0
            LANGUAGES CXX
        )

        add_executable(executable file1.cpp file2.cpp file3.cpp)
        #You can have as many executables as possible (maybe for testing)

        target_compile_options(executable "-Wall -Wextra -Wpedantic -Werror") 
        #For a particular executable  
        

CMake generation

For make...


        > mkdir build
        > cd build
        > cmake .. -G "Unix Makefiles"
        > make    
        

CMake generation

For ninja...

        > mkdir build
        > cd build
        > cmake .. -G Ninja
        > ninja    
        

CMake generation

Internally inside an IDE

IDE

An integrated development environment (IDE) enables programmers to consolidate the different aspects of writing a computer program.

IDEs increase programmer productivity by combining common activities of writing software into a single application: editing source code, building executables, and debugging.

Visual Studio Code

  • Popular cross-plattform IDE
  • Configurable through extensions
  • Integrates with CMake, git...

Visual Studio Code

Install extensions

  • C/C++ Extension Pack
  • CMake
  • CMake tools

Visual Studio Code

For each project:

  • Create a CMakeLists.txt on its root folder
  • In Visual Studio Code, open that folder
  • Let Visual Studio Code find the file and create a compilation system

Example

You can have a look at simple CMakeList.txt examples that work in Visual Studio Code on the course's Github repository