Lecture 02.2
Lecture 02.2
145
Compiling C++ files Hello World 2.0
In C++ the code is usually separated into header files (.h/.hpp) and
implementation files (.cpp/.cc):
sayhello.hpp
#include <string_view>
void sayhello(std::string_view name);
sayhello.cpp
#include "sayhello.hpp"
#include <iostream>
void sayhello(std::string_view name) {
std::cout << "Hello " << name << '!' << std::endl;
}
Other code that wants to use this function only has to include sayhello.hpp.
146
Compiling C++ files Compiler
Compiler
Reminder: Internally, the compiler is divided into Preprocessor, Compiler, and
Linker.
Preprocessor:
• Takes an input file of (almost) any programming language
• Handles all preprocessor directives (i.e., all lines starting with #) and macros
• Outputs the file without any preprocessor directives or macros
Compiler:
• Takes a preprocessed C++ (or C) file, called translation unit
• Generates and optimizes the machine code
• Outputs an object file
Linker:
• Takes multiple object files
• Can also take references to other libraries
• Finds the address of all symbols (e.g., functions, global variables)
• Outputs an executable file or a shared library
147
Compiling C++ files Compiler
Preprocessor (1)
Preprocessor directive #include: Copies (!) the contents of a file into the
current file.
Syntax:
• #include "path" where path is a relative path to the header file
• #include <path> like the first version but only system directories are
searched for the path
In C++ usually only header files are included, never .cpp files!
148
Compiling C++ files Compiler
Preprocessor (2)
149
Compiling C++ files Compiler
Preprocessor (3)
150
Compiling C++ files Compiler
Compiler
• Every translation unit (usually a .cpp file) results in exactly one object file
(usually .o)
• References to external symbols (e.g., functions that are defined in another
.cpp) are not resolved
mul.cpp
int add(int a, int b);
int mul(int a, int b) {
if (a > 0) { return add(a, mul(a - 1, b)); }
else { return 0; }
}
Linker
• The linker usually does not have to know about any programming language
• Still, some problems with your C++ code will only be found by the linker and
not by the compiler (e.g., ODR violations)
• Most common error are missing symbols, happens either because you forgot
to define a function or global variable, or forgot to add a library
• Popular linkers are: GNU ld, GNU gold, lld (by the LLVM project)
152
Compiling C++ files Compiler
153
Compiling C++ files Debugging
• Debugging by printing text is easy but most of the time not very useful
• Especially for multi-threaded programs a real debugger is essential
• For C++ the most used debugger is gdb (“GNU debugger”)
• It is free and open-source (GPLv2)
• For the best debugging experience a program should be compiled without
optimizations (-O0) and with debug symbols (-g)
• The debug symbols help the debugger to map assembly instructions to the
source code that generated them
• The documentation for gdb can be found here:
https://sourceware.org/gdb/current/onlinedocs/gdb/
154
Compiling C++ files Debugging
155
Compiling C++ files Debugging
frame Show the currently selected stack frame, i.e. the current
stack with its local variables. Usually includes the function
name and the current source line. Can also be used to
switch to another frame.
backtrace Show all stack frames.
up Select the frame from the next higher function.
down Select the frame from the next lower function.
watch Set a watchpoint. When the memory address that is
watched is read or written, the debugger stops.
thread Show the currently selected thread in a multi-threaded pro-
gram. Can also be used to switch to another thread.
Most commands also have a short version, e.g., r for run, c for continue, etc.
156
Compiling C++ files Debugging
When this function is called with b==0, the program will crash with a useful error
message.
157
Compiling C++ files Debugging
• Modern compilers can automatically add several runtime checks, they are
usually called sanitizers
• Most important ones:
• Address Sanitizer (ASAN): Instruments memory access instructions to check
for common bugs
• Undefined-Behavior Sanitizer (UBSAN): Adds runtime checks to guard against
many kinds of undefined behavior
• Because sanitizers add overhead, they are not enabled by default
• Should normally be used in conjunction with -g for debug builds
• Compiler option for gcc/clang: -fsanitize=<sanitizer>
• -fsanitize=address for ASAN
• -fsanitize=undefined for UBSAN
• Should be enabled by default in your debug builds, unless there is a very
compelling reason against it
158
Compiling C++ files Debugging
UBSAN Example
foo.cpp
#include <iostream>
int main() {
int a; int b;
std::cin >> a >> b;
int c = a * b;
std::cout << c << std::endl;
return 0;
}
159
Declarations and Definitions
160
Declarations and Definitions Objects
Objects
One of the core concepts of C++ are objects.
• The main purpose of C++ programs is to interact with objects in order to
achieve some goal
• Examples of objects are local and global variables
• Examples of concepts that are not objects are functions, references, and
values
161
Declarations and Definitions Objects
162
Declarations and Definitions Objects
Lifetime
In addition to their storage duration objects also have a lifetime which is closely
related. References also have a lifetime.
• The lifetime of an object or reference starts when it was fully initialized
• The lifetime of an object ends when its destructor is called (for objects of
class types) or when its storage is deallocated or reused (for all other types)
• The lifetime of an object never exceeds its storage duration.
• The lifetime of a reference ends as if it were a “scalar” object (e.g. an int
variable)
Generally, using an object outside of its lifetime leads to undefined behavior.
Lifetime issues are the main source of memory bugs!
• A C++ compiler can only warn about very basic lifetime errors
• If the compiler warns, always fix your code so that the warning disappears
164
Declarations and Definitions Namespaces
Namespaces (1)
Larger projects may contain many names (functions, classes, etc.)
• Should be organized into logical units
• May incur name clashes
• C++ provides namespaces for this purpose
Namespace definitions
namespace identifier {
namespace-body
}
Explanation
• identifier may be a previously unused identifier, or the name of a namespace
• namespace-body may be a sequence of declarations
• A name declared inside a namespace must be qualified when accessed from
outside the namespace (:: operator)
165
Declarations and Definitions Namespaces
Namespaces (2)
Qualified name lookup
namespace A {
void foo() { /* do something */ }
void bar() {
foo(); // refers to A::foo
}
}
namespace B {
void foo() { /* do something */ }
}
int main() {
A::foo(); // qualified name lookup
B::foo(); // qualified name lookup
166
Declarations and Definitions Namespaces
Namespaces (3)
namespace A { namespace B {
void foo() { /* do something */ }
}}
// equivalent definition
namespace A::B {
void bar() {
foo(); // refers to A::B::foo
}
}
int main() {
A::B::bar();
}
167
Declarations and Definitions Namespaces
Namespaces (4)
Code can become rather confusing due to large number of braces
• Use visual separators (comments) at sensible points
• (Optionally) add comments to closing namespace braces
//----------------------------------
namespace A::B {
//----------------------------------
void foo() {
// do something
}
//----------------------------------
void bar() {
// do something else
}
//----------------------------------
} // namespace A::B
//----------------------------------
168
Declarations and Definitions Namespaces
Namespaces (5)
• Always using fully qualified names makes code easier to read
• Sometimes it is obvious from which namespace the names come from in
which case one prefers to use unqalified names
• For this using and using namespace can be used
• using namespace X imports all names from namespace X
• using X::a only imports the name a from X into the current namespace
• Should not be used in header files to not influence other implementation files
namespace A { int x; }
namespace B { int y; int z; }
using namespace A;
using B::y;
int main() {
x = 1; // Refers to A::x
y = 2; // Refers to B::y
z = 3; // ERROR: z was not declared in this scope
B::z = 3; // OK
}
169
Declarations and Definitions Declarations
Declarations
C++ code that introduces a name that can then be referred to is called
declaration. There are many different kinds of declarations:
• variable declarations: int a;
• function declarations: void foo();
• namespace declarations: namespace A { }
• using declarations: using A::x;
• class declarations: class C;
• template declarations: template <typename T> void foo();
• ...
170
Declarations and Definitions Declarations
Declaration Specifiers
Some declarations can also contain additional specifiers. The following lists shows
a few common ones and where they can be used. We will see some more specifiers
in future lectures.
static Can be used for variable and function declarations, affects the
declaration’s linkage (see next slide). Also, objects declared with
static have static storage duration.
extern Can be used for variable declarations in which case it also affects
their linkage. Objects declared with extern also have static
storage duration.
inline Can be used for variable and function declarations. Despite the
name, has (almost) nothing to do with the inlining optimization.
See the slides about the “One Definition Rule” for more
information.
171
Declarations and Definitions Declarations
Linkage
Most declarations have a (conceptual) property called linkage. This property
determines how the name of the declaration will be visible in the current and in
other translation units. There are three types of linkage:
no linkage:
• Names can only be referenced from the scope they are in
• Local variables
internal linkage:
• Names can only be referenced from the same translation unit
• Global functions and variables declared with static
• Global variables that are not declared with extern
• All declarations in namespaces without name (“anonymous namespaces”)
external linkage:
• Names can be referenced from other translation units
• Global functions (without static)
• Global variables with extern
172
Declarations and Definitions Definitions
Definitions
When a name is declared it can be referenced by other code. However, most uses
of a name also require the name to be defined in addition to be declared.
Formally, this is called odr-use and covers the following cases:
• The value of a variable declaration is read or written
• The address of a variable or function declaration is taken
• A function is called
• An object of a class declaration is used
Most declarations are also definitions, with some exceptions such as
• Any declaration with an extern specifier and no initializer
• Function declarations without function bodies
• Declaration of a class name (“forward declaration”)
173
Declarations and Definitions Definitions
174
Declarations and Definitions Definitions
a.cpp
int i = 5; // OK: declares and defines i
int i = 6; // ERROR: redefinition of i
175
Declarations and Definitions Definitions
176
Declarations and Definitions Definitions
177
Declarations and Definitions Header and Implementation Files
When distributing code over several files it is usually split into header and
implementation files
• Header and implementation files have the same name, but different suffixes
(e.g. .hpp for headers, .cpp for implementation files)
• Header files contain only declarations that should be visible and usable in
other parts of the program
• Implementation files contain definitions of the names declared in the
corresponding header
• At least the header files should include some documentation
178
Declarations and Definitions Header and Implementation Files
179
Declarations and Definitions Header and Implementation Files
path/B.hpp
#include "path/A.hpp"
inline int bar() { return foo(); }
main.cpp
#include "path/A.hpp"
#include "path/B.hpp" // ERROR: foo is defined twice
180
Declarations and Definitions Header and Implementation Files
path/B.hpp
#ifndef H_path_B
#define H_path_B
#include "path/A.hpp"
inline int bar() { return foo(); }
#endif
181
Declarations and Definitions Header and Implementation Files
The example CMake project from last lecture shows how header and
implementation files are used. These are the header files:
sayhello.hpp
#ifndef H_exampleproject_sayhello
#define H_exampleproject_sayhello
#include <string_view>
/// Print a greeting for `name`
void sayhello(std::string_view name);
#endif
saybye.hpp
#ifndef H_exampleproject_saybye
#define H_exampleproject_saybye
#include <string_view>
/// Say bye to `name`
void saybye(std::string_view name);
#endif
182
Declarations and Definitions Header and Implementation Files
saybye.cpp
#include "saybye.hpp"
#include <iostream>
183
Declarations and Definitions Header and Implementation Files
184