/flags/cpp/gpp-e-preprocess-only
g++ -E: show the preprocessed C++ source (debug macros and #include)
- tool
- C++
- command
- g++
- flag
- -E
- default
- off: preprocessing is followed by compilation and linking
g++ -E file.cpp runs only the preprocessor and writes the result to standard output: every #include is replaced by the header's text, macros are expanded, #if blocks that are false are removed, and comments disappear. Nothing is compiled. It is the tool for understanding why a macro misbehaves, which header defines something, or which branch of an #ifdef was taken. Add -P to drop the line markers and -o file.ii to save the output.
Macro expansion made visible
The example uses Clang 20 from the Zig 0.15.1 toolchain (installed from PyPI with uv); cxx below is that compiler, and g++ accepts the same options:
cxx() { uv run -q --no-project --python 3.13 --with ziglang==0.15.1 python -m ziglang c++ -fno-color-diagnostics "$@"; }
cat > macros.cpp <<'EOF'
#define SQUARE(x) x * x
#define SQUARE_OK(x) ((x) * (x))
#define NAME "errcodex"
#ifdef FAST
int mode = 1; // fast path
#else
int mode = 0; // default path
#endif
int a = SQUARE(1 + 2);
int b = SQUARE_OK(1 + 2);
const char* n = NAME;
EOF
echo "== g++ -E -P"
cxx -E -P macros.cpp
echo "== g++ -E -P -DFAST"
cxx -E -P -DFAST macros.cpp | grep mode
echo "== line markers without -P"
cxx -E macros.cpp | grep -m1 '^# 1 "macros.cpp"'
echo "== <iostream> expands to"
printf '#include <iostream>\n' > io.cpp
lines=$(cxx -std=c++17 -E -P io.cpp | wc -l)
[ "$lines" -gt 10000 ] && echo "more than 10000 lines"Output
== g++ -E -P int mode = 0; int a = 1 + 2 * 1 + 2; int b = ((1 + 2) * (1 + 2)); const char* n = "errcodex"; == g++ -E -P -DFAST int mode = 1; == line markers without -P # 1 "macros.cpp" == <iostream> expands to more than 10000 lines
SQUARE(1 + 2) expands to 1 + 2 * 1 + 2, which is 5, not 9: the classic missing-parentheses bug, plainly visible in the -E output. The #ifdef branch changes with -DFAST, comments are gone, and a single #include <iostream> becomes tens of thousands of lines.
Useful combinations
| Command | Shows |
|---|---|
| g++ -E file.cpp | Preprocessed source with line markers |
| g++ -E -P file.cpp | Same without line markers, easier to read |
| g++ -E file.cpp -o file.ii | Save it; .ii is the conventional extension for preprocessed C++ |
| g++ -dM -E file.cpp | All macros defined at the end, instead of the code |
| g++ -dM -E -x c++ /dev/null | Predefined macros of the compiler (__cplusplus, __GNUC__...) |
| g++ -E -H file.cpp | Also print the tree of included headers on stderr |
| g++ -M / -MM file.cpp | Make dependency rules (all headers / non-system headers) |
| g++ -E -C file.cpp | Keep comments |
When -E helps
- "was not declared" after a macro: if a name disappears or turns into something else, a macro with the same name is the usual cause. Look at the expanded line; see was not declared in this scope.
- Which header provides a declaration:
g++ -E -Hlists the include tree, and the line markers in-Eoutput show the file where a declaration came from. Missing standard headers often cause no member named X in namespace std. - Platform
#ifdefs: check which branch is active for a given target or-Dset without building. - Bug reports: compiler developers ask for a preprocessed file (
-E, or-save-temps), because it reproduces the problem without your include paths.
Pitfalls
- The output depends on every flag that affects the preprocessor:
-std,-D,-I, the target. Use the same flags as the real build, or the result can differ. -Edoes not check that the code is valid C++; syntax errors appear only when compiling.- The
__cplusplusvalue changes with-std(for example201703Lfor C++17), which switches code paths in headers; see -std=c++17.
FAQ
Why is the -E output of a small file so long?
Every #include is expanded recursively, so including <iostream> or <string> pulls in tens of thousands of lines of the standard library. Pipe the output through tail, grep for your own identifiers, or preprocess a file without system headers.
What are the lines starting with # in the output?
Line markers such as # 3 "main.cpp" tell the compiler which file and line the following code came from, so error messages point to the original source. -P removes them when you only want to read the code.
How do I see the value of a macro?
g++ -dM -E file.cpp lists all macros defined at the end of preprocessing, including predefined ones; grep for the name. For a single expression, put it in a small file and run g++ -E -P on it.