← C++, End to End

C++, End to End

The Absolute Basics

A C++ program is a set of text files the compiler turns into a single executable in two separate stages, and understanding that split saves you a lot of confusion later when an error message doesn't make sense. First, each .cpp file (a translation unit) gets compiled on its own into an object file. Then a separate program, the linker, stitches all the object files together into one executable, resolving which function definition goes with which function call across files.

Execution always starts in a function named main. Every C++ program needs exactly one. It returns an int: 0 conventionally means success, anything else means failure, and the operating system or calling script can check that return value.

c++ · live, editable, runnableOpen in Compiler Explorer ↗

A few things to notice in that example. #include <iostream> isn't a C++ statement, it's a preprocessor directive (the preprocessor gets its own chapter later), and it pulls in the declarations needed for std::cout. The std:: prefix means "look inside the std namespace," which is where almost everything the standard library gives you lives. And return 0; is technically optional in main specifically (the compiler inserts an implicit return 0; if you fall off the end), but write it anyway. Relying on that special case reads as an accident, not a decision.

Statements and the structure of a program

A statement is the smallest complete instruction: a variable declaration, an expression followed by a semicolon, a function call, a loop, a conditional. Statements run top to bottom inside a function unless something (a loop, a branch, a function call) changes that order. Curly braces { } group statements into a block, and a block is itself something you can use almost anywhere a single statement is expected.

Whitespace, for the most part, doesn't matter to the compiler. Indentation is entirely for humans. That's exactly why consistent indentation matters so much in practice: nothing enforces it, so a badly indented file actively lies about its own structure.

Comments

c++
// A single-line comment: everything after // on this line is ignored.

/* A multi-line comment.
   Everything between the start and end markers is ignored,
   including line breaks. */

int x = 5; // trailing comments are fine too

Comments should explain why, not what. "increment i" above i++; tells you nothing the code doesn't already say. A comment explaining why a loop starts at 1 instead of 0, or why a particular edge case is handled the way it is, earns its place.

Variables and initialization

A variable is a named piece of storage with a type. Declaring one reserves the storage; it doesn't necessarily give it a value.

c++
int a;        // uninitialized: contains garbage, reading it is undefined behavior
int b = 5;    // copy initialization
int c(5);     // direct initialization
int d{5};     // direct list initialization (preferred in modern C++)
int e{};      // value initialization: zero for a fundamental type

Use brace initialization, int d{5};, as your default. It does one thing the older forms don't: it refuses narrowing conversions at compile time.

c++ · live, editable, runnableOpen in Compiler Explorer ↗

That compile error is a feature. A silent truncation like the first line is exactly the kind of bug that survives code review and shows up as "weird" behavior three months later.

Try it yourself: delete the `int b{3.7};` line above so it compiles, run it, then bring it back and read the actual compiler error. Get used to what a narrowing-conversion error looks like now, you'll see it again.

Basic input and output

std::cout writes to standard output, std::cin reads from standard input, and both use the << / >> operators (overloaded for this purpose, which is a preview of operator overloading, covered much later). Chain them to output or read multiple things in one statement.

c++ · live, editable, runnableOpen in Compiler Explorer ↗

That example uses using namespace std; to drop the std:: prefix everywhere. Don't do this in real code, especially not in a header file where it leaks into every file that includes it. It's fine in a 10-line teaching example; it's a real liability once a codebase has thousands of names in scope and two libraries happen to define something with the same name.

Compiling a program, end to end

Four distinct stages happen when you run a single compile command, even though most compilers hide the seams:

  • Preprocessing. #include, #define, and other # directives are resolved textually. Comments are stripped. The output is one long, expanded, pure-C++ text stream.
  • Compilation proper. The preprocessed text is parsed, type-checked, and turned into an object file (machine code plus a symbol table of names it defines and names it still needs) for that one translation unit only.
  • Linking. The linker takes every object file plus any libraries, matches each undefined symbol to a definition somewhere, and produces one executable. "undefined reference" errors happen here, not during compilation, which is why they can look confusing: the code compiled fine, the definition just never turned up anywhere the linker looked.
  • Loading. When you actually run the executable, the operating system maps it into memory and starts executing at the entry point (which calls your main, after some runtime setup).
source.cppyour code + #includes
preprocessorexpands includes/macros
compilerparses, type-checks, emits object code
source.oone translation unit's machine code
linkermerges all .o files + libraries
executablewhat the OS actually runs

Try it yourself: if you have g++ locally, run `g++ -c main.cpp` to stop after the compile stage (you'll get main.o but no executable), then `g++ main.o -o main` to run the linker separately. Seeing the two stages as two commands makes "undefined reference" errors much less mysterious the first time you hit one for real.


Next: functions, and how a program stops being one file.