Contact
Evil Genius SocietyHomeLearnForumLeaderboardSocialsMessages
← Back to module

Lesson 2 of 6

g++ hello.cpp -o hello looks like one step. It's actually four programs running in sequence, and knowing which is which is how you'll diagnose build problems for the rest of your career.

hello.cpp
   │  1. preprocessor   handles #include, #define — pure text substitution
   ▼
hello.i        (expanded C++ source, still text)
   │  2. compiler       C++ → assembly, and where type errors are found
   ▼
hello.s        (assembly, still text)
   │  3. assembler      assembly → machine code
   ▼
hello.o        (object file — machine code, but incomplete)
   │  4. linker         stitch object files + libraries together
   ▼
hello          (executable)

You can stop g++ after any stage and look at what it produced. That's the rest of this lesson.

Stage 1: the preprocessor

The preprocessor handles every line starting with #. It doesn't understand C++ at all — it does text substitution and nothing more. #include <iostream> means, quite literally, "paste the entire contents of the iostream header here."

Run this to see the result:

g++ -E hello.cpp -o hello.i
wc -l hello.i

-E means "stop after preprocessing." wc -l counts lines. Your six-line program becomes tens of thousands of lines — that's <iostream> and everything it includes, pasted in.

This is why C++ build times get bad: every file that includes a big header pays for the whole thing, every time it's compiled.

Stage 2: the compiler

This is the stage people mean when they say "the compiler." It parses the expanded source, checks types, applies optimizations, and emits assembly — a human-readable text form of machine instructions.

g++ -S hello.cpp -o hello.s

-S means "stop after compiling." Open hello.s and you'll see lines like movq, call, ret. You don't need to read assembly, but it's worth looking at once: this is the shape of what your CPU actually does.

Nearly every error you meet as a beginner comes from this stage — type mismatches, undeclared names, missing semicolons.

Stage 3: the assembler

The assembler translates that assembly text into actual machine code bytes, producing an object file.

g++ -c hello.cpp -o hello.o

-c means "compile and assemble, but don't link." The result is real machine code — but it's not runnable yet. Your hello.o contains the compiled body of main, and a note saying "I call something named operator<<, and I don't know where it lives."

This stage almost never fails. If your code assembled, the hard part is behind you.

Stage 4: the linker

The linker resolves those notes. It takes all your object files, plus the libraries you use (like the C++ standard library, which supplies std::cout), and connects every call to the actual code that implements it. Then it writes the final executable.

g++ hello.o -o hello

The linker knows nothing about C++ types or syntax. It works with names and addresses. That's why linker errors look so different from compiler errors — the subject of lesson 2.6.

Why this matters

When a build fails, the message alone tells you which stage broke, and each stage fails for completely different reasons:

| Stage | Typical failure | | --- | --- | | Preprocessor | fatal error: foo.h: No such file or directory | | Compiler | error: 'x' was not declared in this scope | | Assembler | (essentially never) | | Linker | undefined reference to 'foo()' |

Knowing "this is a linker error, not a compiler error" cuts your search space enormously before you've even read the details.

Try it

Run all four commands above on your hello.cpp, then look at the sizes of what came out: ls -l hello.cpp hello.i hello.s hello.o hello. Which file is biggest, and why is that surprising at first?

Cleaning up

Those intermediate files aren't needed. Remove them:

rm -f hello.i hello.s hello.o

Checkpoint

  • A build is four stages: preprocessor → compiler → assembler → linker.
  • -E, -S, and -c stop after stages 1, 2, and 3 so you can inspect the output.
  • The preprocessor is pure text substitution; #include literally pastes a file in.
  • Type errors come from the compiler; "undefined reference" comes from the linker.
  • Identifying the failing stage narrows a build problem before you read any details.

Questions about this lesson

Log in to ask a question.