Writing, compiling and linking C++ programs

Programming

Some C++ basics and resources

C++ Tutorial

C++ Reference

C++ resources by Bjarne Stroustrup

Organizing Code Files in C and C++

Compiling and linking

../../_images/compilingAndLinking.png

A talk from the Master

The Beauty and Power of “Primitive” C++: Plenary talk given by Bjarne Stroustrup at CppCon2020 (link to the video)

Building software

Any piece of software, like executables or libraries, built from raw source code are called ‘targets’ by the build systems. To build targets, the build system needs information such as:

  • The locations of tool chain components (e.g. C++ compiler).

  • Source code locations.

  • Code dependencies.

  • External dependencies.

  • Where those dependencies are located.

  • Which targets should be built.

  • Where targets should be built.

  • Where targets should be installed.

The required commands to generate a ‘target’ are well-defined. However the process to call them may be different. To illustrate this, this tutorial uses the basic Hello, world! C++ program using different ways to generate the executable file from its source C++ code. The section is based on A gentle introduction to catkin tutorial by Jonathan Bohren.

Exercise

Download the following source code in your working folder.

The source code provided contains a different folder for each alternative way. The executable will be generated:

  • By writing the dependencies by hand.

  • By using a Makefile.

  • By using CMake.

  • By using Catkin.

  • By using catkin_make.

  • By using the catkin tools.

Building by hand

  • Dependencies are explicitly written.

  • Prone to errors due to changes in the library paths.

  • Does not scale to large projects.

Generate the executable:

$ cd by_hand
$ g++ hello_world_node.cpp -o hello_world_node -I/opt/ros/noetic/include -L/opt/ros/noetic/lib -Wl,-rpath,/opt/ros/noetic/lib -lroscpp -lrosconsole

Start by running roscore:

$ roscore

Then, in another terminal, run the executable and return to the main folder (once the execution is terminated by pressing Ctrl+C):

$ ./hello_world_node
$ cd ..

Building with a Makefile

  • The GNU Make utility specifies rules to compile and link code using a Makefile file.

  • Makefiles separate the compilation and linking steps into two inter-dependent rules.

  • Makefiles make apparent which libraries are needed and where they are expected to be.

The Makefile for this example is:

 1# Declare our preferred compiler
 2CC=g++
 3# Declare the compile flags
 4CFLAGS=-I/opt/ros/noetic/include
 5# Declare the linker flags
 6LDFLAGS=-L/opt/ros/noetic/lib -Wl,-rpath,/opt/ros/noetic/lib -lroscpp -lrosconsole
 7
 8# A rule which satisfies any dependencies with the ".o" extension by compiling
 9# (but not linking) the corresponding ".cpp" file with the same basename
10%.o: %.cpp
11       $(CC) -c -o $@ $< $(CFLAGS)
12
13# A rule to build the hello_world_node program
14hello_world_node: hello_world_node.o
15   $(CC) -o hello_world_node hello_world_node.o $(LDFLAGS)

Generate the executable:

$ cd by_makefile
$ make

Execute and return to the main folder:

$ ./hello_world_node
$ cd ..

Building with a Makefile and the pkg-config utility

  • Many libraries provide .pc files which describe their include directories, link directories, libraries, and other required flags, e.g. /opt/ros/noetic/lib/pkgconfig/roscpp.pc.

  • The pkg-config utility locates and manages them.

The Makefile for this example is now:

 1# Declare our preferred compiler
 2CC=g++
 3# Declare the compile flags
 4CFLAGS=$(shell pkg-config --cflags roscpp)
 5# Declare the linker flags
 6LDFLAGS=$(shell pkg-config --libs roscpp)
 7
 8# A rule which satisfies any dependencies with the ".o" extension by compiling
 9# (but not linking) the corresponding ".cpp" file with the same basename
10%.o: %.cpp
11       $(CC) -c -o $@ $< $(CFLAGS)
12
13# A rule to build the hello_world_node program
14hello_world_node: hello_world_node.o
15   $(CC) -o hello_world_node hello_world_node.o $(LDFLAGS)

Generate the executable:

$ cd by_makefile2
$ make

Execute and return to the main folder:

$ ./hello_world_node
$ cd ..

Building with CMake

  • CMake is a cross-platform tool to build, test and package software.

  • CMake does not directly build the software, but generates the project of the most common platforms to build them, i.e. it writes the GNU Makefiles for you (and CMake is just better at it than you are!…) or generates projects for other platforms (e.g. Visual Studio projects in Windows or XCode projects in MacOS).

  • All the build rules for a given directory are specified in a file named CMakeLists.txt, which are more easily specified and that have commands like find_package(…) that gets not only the build flags for a package, but also all the flags for its dependencies.

The CMakeLists.txt for this example is:

 1# Declare the version of the CMake API for forward-compatibility
 2cmake_minimum_required(VERSION 2.8)
 3
 4# Declare the name of the CMake Project
 5project(hello_world_tutorial)
 6
 7# Find and get all the information about the roscpp package
 8find_package(roscpp REQUIRED)
 9
10# Add the headers from roscpp
11include_directories(${roscpp_INCLUDE_DIRS})
12# Define an execuable target called hello_world_node
13add_executable(hello_world_node hello_world_node.cpp)
14# Link the hello_world_node target against the libraries used by roscpp
15target_link_libraries(hello_world_node ${roscpp_LIBRARIES})

Generate the executable (following the common practice of keeping all the generated products in a directory called build):

$ cd by_cmake
$ mkdir build
$ cd build
$ cmake ..
$ make

Execute and return to the main folder:

$ ./hello_world_node
$ cd ../..