GCC


Introduction

The GNU Compiler Collection (https://gcc.gnu.org/) is the default compiler on GNU/Linux operating systems like Ubuntu and Raspbian. It evolved from the GNU C Compiler project and kept its acronym while adding support for programming languages like C++, Objective-C, and Go.

Switching between GCC versions in Ubuntu

The versions of GCC in the Ubuntu main repositories are

  • Ubuntu 16.04: GCC 5.5
  • Ubuntu 18.04: GCC 7.3


Sometimes, however, you need to use a different version of GCC, and you want to easily switch between versions. For example, between GCC 6 and GCC 7 in Ubuntu 18.04. To do so, you can use the update-alternatives tool. Here is how to configure update-alternatives for GCC.

First, make sure that no alternatives group for GCC exists by typing

sudo update-alternatives --remove-all gcc

which should result in the error message

update-alternatives: error: no alternatives for gcc

Now enter

sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-6 10
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 20
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-6 10
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-7 20

where the last number in each line is the priority of the corresponding alternative. You can check that the alternatives groups are configured by entering

update-alternatives --list gcc
update-alternatives --list g++


You can now select the desired GCC version via the interactive chooser

sudo update-alternatives --config gcc
sudo update-alternatives --config g++

or directly via

sudo update-alternatives --set gcc [/usr/bin/gcc-6 | /usr/bin/gcc-7]
sudo update-alternatives --set g++ [/usr/bin/g++-6 | /usr/bin/g++-7]

where only one of the options inside the square brackets must be entered.


Usage

Some Useful Command-Line Options

-g generate debug output
-c compile only (do not link)
-O2 optimize for size and speed
-std=c++14 enable C++14 features
-x process for the given programming language
-c -x c++-header -o m.gch m.h turn m.h into a precompiled header (m.gch)
-Wl,-rpath,<path> -L<path> -lmylib make the linker embed the given relative path to the given library

The full list of command-line options can be viewed here.

Gotchas

Here are some of the fine points of using GCC:

Linker flags come last

When compiling source files into object files or linking object files into an executable or library file, the placement of the linker flags relative to the input file within the GCC command matters! External libraries that are being linked to need to be specified AFTER THE OBJECT FILE that refers to the symbols in the library. The linker does not know it needs to link to the external library before it sees the references in the object file. You will see error messages that complain about "undefined reference to" some external symbol if the library is not specified, or specified too early.

From the GNU documentation: "It makes a difference where in the command you write this option; the linker searches and processes libraries and object files in the order they are specified. Thus, foo.o -lz bar.o searches library z after file foo.o but before bar.o. If bar.o refers to functions in z, those functions may not be loaded."

By the way, you can use the ldd command line tool to check if all the linked libraries can be located by the dynamic loader.

C++ ABI change in GCC 5.1

The ABI for C++11 changed in GCC 5.1. This requires that libraries that were built with an older version of GCC need to be linked against after compiling with the environment variable set to "0". That is,

g++ -D_GLIBCXX_USE_CXX11_ABI=0 my_code.cpp -o my_code.o

See https://gcc.gnu.org/gcc-5/changes.html#libstdcxx for more information.

Hiding internal implementation details

When creating a library you may want to remove internal implementation details to reduce the size of the library or to protect your intellectual property. Use the compiler flag -fvisibility=hidden in order to mark generated symbols as local (= not to be exported) by default. In your code mark selected functions, methods, or variables as global (= to be exported) by placing the GCC visibility attribute

__attribute__ (( visibility("default") ))

in front of the declaration. See this GCC Wiki entry for more information on the visibility attribute.

We're not done yet. The library created by the linker still contains all the symbols that were marked as local. You can check whether symbols inside the binary are local or global (exported) by inspecting the library with the command-line tool nm:

nm --demangle my_library.so

where the option --demangle is for displaying C++ symbols with the familiar double-semicolon scope separators instead of cryptic alphanumeric sequences. Symbols are listed with a letter next to it, like 'A', 'C' or 'R'. The letter indicates the type of the symbol. If the letter is in upper case, the symbol is global, otherwise it is usually local. Refer to the nm manual for details. In order to purge our local symbols from the library file, we use the command line tool "strip":

strip -x my_library.so

where the option -x specifies that all non-global symbols should be removed.

Specifying the path to your library

If you don't want to place your shared libraries into one of the default folders where the library loader of the operating system can find them (on Linux systems these folders are specified in /etc/ld.so.conf and in additional .conf files lying in /etc/ld.so.conf.d/) then you need to specify the paths to your libraries either by setting the LD_LIBRARY_PATH environment variable before launching the executable that links to the library, or by baking the (relative) path to the library into the executable itself.

Use the rpath option in the linking step of your executable to specify the relative path from your executable to the library. For example,

g++ class1.o class2.o -Wl,-rpath,../../my_libs -L../../my_libs -lMyLib -o my_app

creates the executable my_app that links to the dynamic library libMyLib.so located in the folder ../../my_libs, relative to the executable. If you are not familiar with -Wl; it denotes the beginning of a comma-separated sequence of options to be passed to the GCC linker (as a whitespace-separated sequence).

On Linux, you can use the readelf command-line tool to verify that the correct library search paths are baked into the generated ELF binary:

readelf -d <path to library or executable>

On macOS, look for the keyword LC_RPATH in the output of

otool -l <path to library or executable>



Sanitizers

On Ubuntu 18.04, you can install Address Sanitizer and Undefined Behavior Sanitizer for GCC 7.x via

sudo apt install libubsan0 libasan4


Address Sanitizer

This sanitizer catches certain problems related to memory management. It is instrumented into the executable with the following GCC flag

-fsanitize=address

which needs to be specified separately for the compiler and the linker.

A number of options can be passed to the address sanitizer either via the ASAN_OPTIONS environment variable or by embedding the following function:

extern "C" {
const char* __asan_default_options() {
  return "verbosity=1:help=1"; // add options here
}
}

For more information visit https://github.com/google/sanitizers/wiki/AddressSanitizer

Undefined Behavior Sanitizer

This sanitizer is for catching undefined behavior in C/C++ code. It is instrumented into the executable by passing

-fsanitize=undefined

to both the compiler and the linker.

For more information see the GCC documentation page on instrumentation options.