Getting Started

How C++ Compilation Works

C++ code begins as just normal text that you write into a file. It's not something the computer will understand on its own. Many tools need to translate it, bit by bit, in steps until it becomes binary code that a machine can understand. The whole process is known as compilation, although there are a few distinct stages, in fact:

The first stage is that of the preprocessor. The job of the preprocessor is to transform the source text before compiling it, according to instructions known as preprocessor directives, which always start with the # symbol. The most common instruction is that which includes external files within the source text, and the command for that is #include.

#include <iostream>

int main()
{
    std::cout << "Hello\n";
    return 0;
}

So when the preprocessor sees the directive to #include <iostream>, it finds the header file which declares the standard input/output facilities and inserts those declarations into the program source text. It's only after this that the rest of the translation will understand the names of things like std::cout. If the preprocessor was not told to #include <iostream>, then the next part of the translation that processes the text will generate an error report of "std::cout has not been declared" and the compilation will cease at that point.

After preprocessing the source text, the product is known as a translation unit. It's the compiler proper that is next to take that translation unit and produce object code, some raw machine code which still has a few references to functions/variables which are not contained within it, but in other files; so for the program above the compiler generates machine code that knows to call the insertion operator on the output stream and to return out of main, but it doesn't know where those functions/library facilities physically are in the machine, it just knows that they should be called.

When you have a C++ program that calls functions which are defined in separate source files, then a second source file will likely need to be compiled independently:

// main.cpp
#include <iostream>

int add(int x, int y);   // declaration only

int main()
{
    std::cout << add(3, 4) << '\n';
    return 0;
}
// add.cpp
int add(int x, int y)
{
    return x + y;
}

You would preprocess the individual files and then compile them, with C++ code often having multiple separate source files that need to be combined. When this separate source file is compiled it becomes an object file that contains a call to a function called add; there is no actual code to that function included. The second source file is also compiled, and now you have an object file with the actual code to add written within it. They both know about one other function, that between them, but not where they are.

The third and final part of the process, known as linking, is what combines all of the object files into a single executable file for you and the standard libraries as well. The linker takes the call to the add function that it finds within the first object file, and it matches that against the actual code found in the second object file and fills in the memory addresses in the executable. If the linker can't find a definition for any of the called functions, then it generates an "undefined reference" error and refuses to produce the executable code.

The sequence of steps described still occurs even if the source text for the whole program is written in a single file. It will be preprocessed as usual with its include directives, then it will be compiled into an object file and then finally the linker will assemble that single object file with the standard library, for the same reason that it always has done. The only thing that's different is that there is only the one object file to be processed.

When you try and pass both a declaration and a definition, it’s not the case that the definition overrules anything the declaration did. If instead, the declaration in main.cpp had been int add(int x, int y, int z) while the second source file still only accepted two parameters, then the compiler would accept this and only a linker error would result, saying it couldn't find a definition for anything that called the function that required three parameters. Therefore, it’s vital that your declarations and definitions correspond.

Whenever the process has found an error at any one step, then all subsequent steps are not run. A missing semicolon in your code will throw a compilation error at compile time instead of at the linking stage; an undefined reference error will occur when the program is linked. Messages will often be emitted from different stages and refer back to that stage so that a single mistake could apparently occur at various points in the process depending on whether the preprocessor or the compiler is the first tool to flag an issue.

The complete path to a running program is the pipeline of: the preprocessor expands directives, the compiler makes object code out of translation units, the linker produces executable programs out of object files and libraries; the executable then can be loaded onto the memory for the operating system to execute starting with main.

Exercise: How C++ Compilation WorksStarter code, hints and solutionOpenClose
Guided practiceBeginner · 10–15 min

Practice: How C++ Compilation Works

Work through the mission, reveal hints only when needed, and compare your result with the model solution.

Your challenge

Compile this deliberately incomplete single-file program in two stages: object compilation, then linking. Explain why the declaration permits add(3, 4) to compile, add the missing definition, and predict the repaired output.

Mission checklist

  1. Identify the declaration of add and the call that uses it.
  2. Explain why the missing definition is detected when linking.
  3. Supply the function body, compile and link, then compare the printed sum with your prediction.

Starter program

Intentional linker-error exercise: this file compiles to an object file, but cannot link by itself because add is declared and not defined. Diagnose the missing definition, then compare with the complete solution. No executable is produced until the error is fixed.

// main.cpp
#include <iostream>

int add(int x, int y);   // declaration only

int main()
{
    std::cout << add(3, 4) << '\n';
    return 0;
}

Hints

Hint 1

Look at int add(int x, int y);: the semicolon makes it a declaration, not a function body. This lets the compiler check add(3, 4).

Hint 2

The linker still needs a definition of add. Find the missing body or source file; changing the arguments alone cannot supply that definition.

Worked solution

The declaration permits compilation of the call; the added add definition supplies the implementation needed by the linker. This single-file solution links successfully and prints 7.

// main.cpp
#include <iostream>

int add(int x, int y);   // declaration only

int main()
{
    std::cout << add(3, 4) << '\n';
    return 0;
}

int add(int x, int y)
{
    return x + y;
}

Example output

7

0 comments

No comments yet. Be the first.

Add a comment

About CppLearn

CppLearn is an interactive, free reference for modern C++. In each lesson, you'll find out how the language works, rather than just lines of code that you can copy and paste. We cover concepts starting from beginner lessons on fundamentals and proceed to templates, move semantics, smart pointers, and areas that usually trap or scare C++ learners.