When you run g++ main.cpp -o myapp, several things happen in sequence. Understanding that sequence helps you debug build errors and structure larger projects.
Step 1: Preprocessing
The preprocessor handles lines that start with #. #include directives are replaced with the contents of the included file. #define macros are substituted. The result is a single expanded source file.
This is why including a header file makes its declarations available — you're literally copying the declarations into your file before compilation.
Step 2: Compilation
The compiler converts your C++ source into assembly language, then into machine code — a file called an object file (.o or .obj). It does this for each .cpp file independently.
During this step, the compiler checks your code for syntax errors and type mismatches. If a function is declared (in a header) but not defined yet, that's fine — the compiler trusts that the definition will appear somewhere.
Step 3: Linking
The linker takes all the object files and combines them into a single executable. It resolves references: if main.cpp calls a function defined in utils.cpp, the linker connects them.
This is why "undefined reference" errors happen at link time, not compile time. The compiler saw the declaration and said "fine, I'll trust you." The linker looked for the definition and couldn't find it.
Header files vs source files
- •Header files (.h or .hpp): contain declarations — function signatures, class definitions, constants. Included in other files.
- •Source files (.cpp): contain definitions — the actual function bodies and variable implementations. Compiled separately.
The rule: put declarations in headers, definitions in source files. Include the header wherever you need the declaration.
// math_utils.h
int add(int a, int b); // declaration
// math_utils.cpp
#include "math_utils.h"
int add(int a, int b) { return a + b; } // definition
// main.cpp
#include "math_utils.h"
int main() { cout << add(3, 4); }Compiling multiple files
g++ main.cpp math_utils.cpp -o myapp
Or compile separately then link:
g++ -c main.cpp -o main.o g++ -c math_utils.cpp -o math_utils.o g++ main.o math_utils.o -o myapp
Make and CMake
For projects with many files, build tools automate this. Make uses a Makefile to define build rules. CMake generates build files for multiple platforms. Most real C++ projects use CMake — it's worth learning once you have a few source files.
Understanding the build system turns mysterious errors into diagnosable problems. "Undefined reference to X" means the linker can't find X's definition. "Redefinition of X" means X is defined in multiple places. Once you understand compilation and linking, these messages point directly to the fix.