5.3.9 · D1Build Systems & Toolchain

Foundations — CMake build types — Debug, Release, RelWithDebInfo

1,849 words8 min readBack to topic

Before you can understand the parent note, you need to know what each little scribble (-g, -O2, -DNDEBUG, CMAKE_CXX_FLAGS_<TYPE>) actually is. This page builds every one of them from zero, in the order they stack on top of each other.


0. The picture the whole topic lives inside

Everything here is about one journey: source code → machine code → running program. Let's draw that pipeline first, because every symbol we meet is a knob on some stage of it.

Figure — CMake build types — Debug, Release, RelWithDebInfo

1. What is a "flag"?

The picture: a command line is a base word (g++ main.cpp) followed by a growing list of these dash-words.


2. The -O symbol — optimization level

Why does the topic need this tool, and not just "make it fast"? Because there is no free lunch: making code run faster means the compiler reorders and deletes instructions. That rearrangement is exactly what makes a debugger confused later. So -O is the single knob that trades runtime speed against debuggability. The whole build-type idea exists to pick a spot on that trade-off.

Figure — CMake build types — Debug, Release, RelWithDebInfo

3. The -g symbol — debug information

Why this tool? Machine code has no idea what a "variable name" or "line number" is — those are human ideas that normally get thrown away. If your program crashes and you want the debugger to say crashed in parse() at line 12 instead of crashed at address 0x4a11c8, you need that map. -g is the request to keep it.


4. -O and -g are orthogonal — the 2D picture

The parent note leans hard on the word orthogonal. Let's earn it.

Figure — CMake build types — Debug, Release, RelWithDebInfo

5. What is a macro? (needed before -DNDEBUG)

Why does the topic need this? Because C and C++ ship a safety tool called assert() that is switched off by exactly this macro. To understand -DNDEBUG you must first meet assert.


6. assert() and how NDEBUG silences it

Here is the rule the standard library follows:

So -DNDEBUG removes every assert from the final binary. Shipping builds do this for speed; debug builds leave asserts in as a safety net.


7. Variables named CMAKE_CXX_FLAGS_<TYPE>

Now we can read the scary-looking variable names.

Degenerate case you must know: if CMAKE_BUILD_TYPE is empty (the silent default!), then is nothing, so the second box is empty, so you get only CMAKE_CXX_FLAGS — meaning no -O, no -g at all. Your "Release" that you forgot to set is secretly an -O0 build. This is the single most common CMake performance trap.


8. Two flavours of generator (a heads-up)

You don't need the details yet — just know the word "generator" so the parent note's "multi-config trap" section makes sense. The key fact: for multi-config, CMAKE_BUILD_TYPE is ignored and you write --config Release at build time instead.


Prerequisite map

Source code vs machine code

What is a flag - dash word

-O optimization level

-g debug symbols

Orthogonal - independent knobs

Macro - preprocessor replace

-D define a macro

NDEBUG silences assert

assert - self check

CMAKE_CXX_FLAGS_TYPE boxes

CMAKE_BUILD_TYPE picks the box

Generators single vs multi config

CMake build types


Equipment checklist

Cover the right-hand side and test yourself. If any line is fuzzy, re-read its section above before opening the parent note.

A "flag" is
a dash-word after the compiler name that toggles one behaviour (e.g. -O2).
What -O controls
how hard the compiler works to make the code run faster (0 = none, 3 = aggressive, s = smallest).
Why high -O hurts debugging
it reorders and deletes instructions, so the debugger can't line them up with your source.
What -g adds
a symbol table mapping machine instructions back to source lines and variable names.
Runtime cost of -g
zero — it only enlarges the file; the CPU skips over it.
Meaning of "orthogonal" for -O and -g
they're independent knobs; any combination (including both at once) is legal.
What a macro is
a name the pre-processor finds and replaces before real compilation.
What -DNDEBUG does
defines the NDEBUG macro, which makes every assert() expand to nothing.
Does NDEBUG disable your own debug prints
no — only assert() and #ifndef NDEBUG code; your if(verbose) logic is untouched.
CMAKE_CXX_FLAGS vs CMAKE_CXX_FLAGS_<TYPE>
the first is always applied; the second is glued on only for the chosen type.
Final flags formula
flags(T) = CMAKE_CXX_FLAGS + CMAKE_CXX_FLAGS_<T>.
What happens if CMAKE_BUILD_TYPE is empty
only the base box applies → no -O, no -g → effectively -O0.
Which variable picks the type (single-config)
CMAKE_BUILD_TYPE.