Compilation Using g++

Compilation is a process to translate source codes into executables or libraries. A compiled programming language requires source codes to be compiled before they can be executed. C++ is typical compiled programming language.

Mainstream Compilation Systems

There are many mainstream C/C++ compilers available to various operating systems:

  • Cross-platform (Linux native, Mac OS, Windows, etc.)

    • gcc/g++ from GNU GCC

  • Mac OS native

    • The LLVM C/C++ compilers clang/clang++ from the Xcode command-line tools

  • Windows native

    • Visual C++

  • Windows non-native

    • MinGW (MSys2, Git Bash)

    • Cygwin

They are not compatible to each other so you need to stick to one for consistency. In our course, the GNU g++ (part of GCC) is chosen as the only compiler and you must stick to it. Although it is not binary compatible to g++, code that is compilable using LLVM compilers clang++ can be compiled with g++ in most of the case.

Warning

It is not recommended to use any variation of MinGW, such as MSys2, Git Bash to compile your code in this course! It is known to cause a lot of compatibility issues and you must perform a final test on a Linux system (e.g. the SSH server) to make sure that it will work during grading.

Building process of C/C++ projects

General compilation process of C/C++ projects contains three main stages: preprocessing, compilation, and linking.

Preprocessing step

Process preprocessing directives like #include, #define, etc. Performs textual manipulations mostly.

Compilation step

Translate the source code (.cpp) files to object (.o) files.

Linking step

Link object (.o) files to produce an executables (or a library)

The three steps of preprocessing, compilation, and linking can be performed either all at once or separately using the g++ compiler. Typically, the preprocessing and compilation steps are performed together. Therefore, the typical step-wise compilation process involves two steps: preprocessing and compilation, followed by linking.

The g++ command

g++ is the GNU C++ compiler invocation command. It builds C++ source codes to produce libraries or executables. It serves as pre-processor, compiler, and linker for C++ projects.

g++ command-line

A typical g++ compilation command-line looks like:

g++ <options> <arguments>
g++ options

Something started with a hyphen character. They are provided to control the behavior of the compilation. Some options come with their own arguments.

g++ arguments

Everything in the command-line that are not options. They usually specify the source of the compilation. They can be either .cpp file or .o file names.

Example: in g++ -Wall -std=c++14 -o main main.cpp command, -Wall, -std=c++14 and -o main are options and main.cpp is an argument.

Common g++ options

-o

This option specify the name of the output file. It must be followed by a name. For example, -o main to generate a file called main.

-c

This option tell g++ to compile only without linking. It will generate an object (.o) file.

-std=c++14

Use the C++ standard 14 to compile. There are other C++ standards available such as C++ 11, C++ 17, C++20, etc.

-Wall

Show common warning messages. Many warning messages are helpful to give you hints on potential problem in your code.

-g

Store debugging information during compilation to allow future use of debuggers like gdb or valgrind.

-O0, -O1, -O2, -O3

Optimize the generated code. The higher the number, the more optimization will be performed. The default is -O2.

Preprocessor directives

Preprocessor directives are instructions that are mixed in with C++ statements in your source code. They direct the preprocessor to manipulate the text of the source code files before they are compiled. These directives are identified by the hash symbol ‘#’ at the start of the instruction.

Warning

Preprocessor directives are not statements and no semicolons are needed as the termination of directives. They are line based instead.

  • #include

    It is used to copy texts from another file and paste to the location of the directive. You can use either angular brackets <> or double quotation marks "" to include system headers or user headers:

    // user header
    #include "mylib.hpp"
    // system header
    #include <iostream>
    

    Note

    The order of inclusion matters and it is recommended to to start from local headers to system headers. Refer to the best practices documents for more details.

  • #define

    It is used to define macros. Un-parameterized macros without values are preprocessor only flags. Un-parameterized macros with values can be used like a text replacement token or a constant. Parameterized macros work like functions. It is not preferred in the courses! Use functions instead!

    // preprocessor only flags
    #define HEADER_HPP
    // constant like macro
    #define PI 3.14159
    
  • conditional compilation #if, #elif, #else, #ifdef, #ifndef, #endif

    They can be used to direct the preprocessor to include code blocks conditionally. These directives may greatly improve efficiency of the compilation process in large projects in trade of the readability and thus maintainability. In our course, we only use them in header guards.

  • Header guards

    Header guards are the preferred way to prevent header contents from being included more than once. It is preferred over the alternative way using #progma once, which is a non-standard C++ directive.

    #ifndef MYHEADER_HPP
    #define MYHEADER_HPP
    
    // header contents
    
    #endif // MYHEADER_HPP
    

    Warning

    You must add header guards to all user headers!

Styles of building

One step building

You may use a single g++ command to produce the executable from the source code files. In this method, all three stages are invoked by one g++ command.

You can list all cpp files in the command line as arguments and specify the output file name as main:

g++ -std=c++14 -o main main.cpp lib1.cpp lib2.cpp

You may also use wildcard to match all cpp files if only one cpp file contains a main function:

g++ -std=c++14 -o main *.cpp

Note

It will fail if you have multiple main functions in your cpp files.

Modular building

In the modular building approach, the preprocessing + compilation and linking steps are performed separately to provide fine control over arguments and fine grained error handling.

Use the same example with a main.cpp, a lib1.cpp, and a lib2.cpp with paired headers. You can build your main executable in steps:

g++ -std=c++14 -Wall -c main.cpp
g++ -std=c++14 -Wall -c lib1.cpp
g++ -std=c++14 -Wall -c lib2.cpp
g++ -o main main.o lib1.o lib2.o

The first three lines compiles all source code files to the corresponding object files respectively. The last line links all object files into an executable named main.

Warning

Modular compilation is always preferred as every step may trigger errors and they are much easier to fix separately It is complicated but can be automated using building tools such as GNU make, etc.

Pitfalls

  • List header files in the g++ command

g++ -o main main.cpp lib1.hpp lib1.cpp

Common Error Messages From g++

Note

Some error messages are misleading. Thus, do not stick to it if you cannot figure it out. Try other method such as code review to solve the problem instead.

  • Alway solve only one error message and re-compile to see the result

  • Compilation errors

    Error happens during compilation of .cpp files to .o files.

    • undeclared identifier ...

      • Means that the compiler cannot find a matching declaration for an identifier usage

      • Misspelled identifier (variable name, type name, class name, etc) when you declare or use an identifier

      • Forget to include the header that declared the identifier

    • No matching function ...

      • Means that the compiler cannot find a matching declaration for a function call

      • Misspelled function name when you declare or call a function

      • Forget to include the header that declared the function

  • Linker errors

    Error happens during the linking of .o files to an executable. They usually end with ld returned 1 exit status in the last line.

    • undefined reference to ...

      • Means that the linker cannot find a function definition for a given declaration

      • misspelled function name when you declare or define a function

      • Forget to include the .o file where the function definition is

    • multiple definition of ... first defined here

      • Means that you have multiple function definitions with a same name and parameter list

      • You have included a wrong .o file in the g++ command. For example, two .o file each has a main function.

      • A failed function overloading

      • A misspelled function name in the definition so it conflict with another function