The journey from source to a running program
You write human-readable instructions in a .cpp source file. A C++ toolchain processes that source for a target platform and produces a program you can run. In a common multi-file build, the path is source → preprocessing → compilation and assembly → object files → linking → executable → execution. An editor changes the source; it does not change an already-built executable until you build again.
Think of the stages as jobs, not a promise that every compiler creates a visible file at every arrow. A compiler driver may run several tools for you, and an IDE or build system may issue the commands. Modules, precompiled headers, optimisations and platform packaging can alter the details. Ordinary native C++ does not need a universal interpreter or virtual machine to execute.
- Write one or more .cpp source files and any declarations they share in headers.
- Preprocess each source file: handle directives such as #include and conditional compilation.
- Compile and assemble each resulting translation unit, often producing a machine-code object file.
- Link the object files with required library code, resolving references between files.
- Run the resulting executable on a compatible target system and inspect its output or failures.
Three small files that work together
Save these files together. greeting.hpp declares a function named greeting; a declaration tells main.cpp that the function exists and what it returns. The #ifndef / #define / #endif lines are a header guard, which prevents the same header’s declarations from being processed twice within one translation unit. A header does not automatically supply the function’s implementation.
greeting.cpp defines the function and returns a string literal. main.cpp includes <iostream> for std::cout, includes the shared declaration, calls greeting and prints the returned text. The output is Hello, Codectionary! followed by a newline. Both .cpp files must be built and linked; compiling main.cpp alone leaves the function call without its definition.
greeting.hpp — shared declaration
#ifndef GREETING_HPP
#define GREETING_HPP
const char* greeting();
#endifgreeting.cpp — function definition
#include "greeting.hpp"
const char* greeting() {
return "Hello, Codectionary!";
}main.cpp — entry point
#include <iostream>
#include "greeting.hpp"
int main() {
std::cout << greeting() << '\n';
}What preprocessing and compilation do
Before ordinary compilation, the preprocessor handles directives beginning with #. For this example, #include makes the header’s contents available while processing each .cpp file. It is a source-processing step; including greeting.hpp does not execute code and does not make greeting.cpp part of main.cpp. The standard <iostream> header supplies declarations used for output.
After preprocessing, the compiler checks syntax and types and translates each translation unit toward machine code. An assembler may turn generated assembly into an object file. A translation unit is roughly one source file after its includes and other preprocessing have been handled. Some toolchains combine these internal stages rather than saving every intermediate file.
Object files, libraries and the linker
An object file contains compiled code and information about names it defines or still needs. It is generally not a complete, runnable application. In this example, main.o needs greeting, while greeting.o provides it. The linker combines their code and resolves that reference. The toolchain also supplies needed startup and standard-library components; the exact static or dynamic library arrangement depends on the platform and build.
A standard library is the language’s specified collection of facilities such as streams and containers, provided by a toolchain implementation. A library used in a project may also be your own or a third party’s. A build system such as CMake describes targets and dependencies and arranges build commands; it does not replace the compiler or linker. A debugger is a separate tool that can pause a running program, step through code and inspect values.
| Term | What it does here |
|---|---|
| Source code | The editable .cpp and .hpp text you write. |
| Compiler | Checks and translates a source translation unit toward machine code. |
| Object file | Compiled output for one unit, commonly .o or .obj; usually not runnable by itself. |
| Linker | Combines object files and libraries and resolves references to create an executable. |
| Executable | The built program for a particular target platform. |
| Standard library | Specified facilities such as std::cout and std::vector, supplied by an implementation. |
| Build system | Coordinates which files and tools must run when the project changes. |
| Debugger | Helps inspect a program while it runs; it does not compile source code. |
Build and run the example
These commands illustrate GCC’s g++ driver in a POSIX-style terminal with a C++17-capable installation. The -c option stops before linking and -o names an output file. The final g++ command links both object files and the needed C++ support. On macOS, clang++ can commonly use a similar command; on Windows with MinGW, the executable is normally hello.exe and you run it with .\hello.exe in PowerShell. MSVC uses different commands and .obj object files. Follow your installed toolchain’s instructions rather than copying an incompatible command.
For this tiny project, g++ -std=c++17 main.cpp greeting.cpp -o hello can perform the stages in one command. The split commands are shown so you can see where the object files come from. Change the message in greeting.cpp, rebuild its object file and relink before rerunning; the old executable still contains the old result.
POSIX terminal with g++ installed
g++ -std=c++17 -c main.cpp -o main.o
g++ -std=c++17 -c greeting.cpp -o greeting.o
g++ main.o greeting.o -o hello
./helloCompiler, linker and runtime errors are different
A compiler error occurs while checking a source translation unit: for example, writing std::coutt instead of std::cout produces an unknown-name error before an executable is made. Read the first relevant diagnostic and its file and line. A linker error occurs after compilation when a needed definition cannot be found: if you compile only main.cpp in this example, the call to greeting has a declaration but no linked definition. The wording of diagnostics differs by compiler and platform.
A runtime error happens after a program starts. For example, calling scores.at(2) on a std::vector holding one element throws std::out_of_range; if it is not caught, the program terminates. Other runtime problems include invalid input, failed file access and incorrect results. Successful compilation and linking cannot prove a program behaves correctly. Debuggers, assertions and tests help you inspect execution; use std::vector::at when you want an out-of-range access to be reported as an exception.
What to practise next
Create the three files, build them, then deliberately omit greeting.o from the link command and compare the linker diagnostic with a compiler typo. Restore the correct files and commands. Finally, change the returned message and rebuild to confirm that editing source alone does not update an executable.
Once that sequence makes sense, learn functions and standard containers before taking on a larger build system. A project may use many source files or libraries, but the same questions remain useful: which file declares a name, which file defines it, and which build step failed?
Syntax
#include <iostream>
#include "greeting.hpp"
int main() {
std::cout << greeting() << '\n';
}Examples
Call a function defined in another source file
Use the greeting.hpp, greeting.cpp and main.cpp files above; build both .cpp files into one executable.
#include <iostream>
#include "greeting.hpp"
int main() {
std::cout << greeting() << '\n';
}- This file is the entry point of a three-file example. Save it as main.cpp alongside greeting.hpp and greeting.cpp from the guide.
- #include "greeting.hpp" gives the compiler the declaration of greeting. The actual function body lives in greeting.cpp, so that file must also be compiled and linked.
- At runtime, main calls greeting, sends its returned text to std::cout and appends a newline. With all files linked, the terminal displays Hello, Codectionary!.
- If the linker cannot find greeting, first check whether greeting.cpp was included in the build; adding another #include to main.cpp is not the fix.
Best practices
- Distinguish a compile error from an unresolved reference at link time and a failure after execution starts.
- Keep declarations in guarded headers and definitions in an appropriate source file for a simple multi-file project.
- Rebuild after changing source; read toolchain-specific diagnostics rather than expecting identical wording everywhere.
Runtime & versions
Compiled native applications. Feature availability depends on the language and runtime version used by your project; consult the official reference below.
ISO C++ resources