Home / Alt manpages / aarch64-linux-gnu-cpp-13(1)

  • aarch64-linux-gnu-cpp-13(1)
  • User command
  • linux

Preprocess Cross-Compiled C with aarch64-linux-gnu-cpp-13

aarch64-linux-gnu-cpp-13 expands an AArch64 C or C++ file so you can see exactly which headers and macros the cross toolchain actually picked. Useful the moment a build behaves differently on target than it did on your workstation. The examples use the installed aarch64-linux-gnu-cpp-13 from GCC 13.3.0 on Ubuntu.

Allow about fifteen minutes. You need a shell and the cpp-aarch64-linux-gnu and cpp-13-aarch64-linux-gnu packages already installed. No command below needs root. This preprocesses source only: it never compiles, links, installs headers or changes a build.

1. Confirm the binary and version

Start with a read-only version check. The suffixed command pins the GCC major version explicitly; the unsuffixed aarch64-linux-gnu-cpp alias is also installed here:

$ command -v aarch64-linux-gnu-cpp-13
/usr/bin/aarch64-linux-gnu-cpp-13
$ aarch64-linux-gnu-cpp-13 --version
aarch64-linux-gnu-cpp-13 (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0
$ aarch64-linux-gnu-cpp-13 -dumpmachine
aarch64-linux-gnu

The version matters the moment you compare generated output between build hosts. Never assume the host compiler and this target preprocessor share the same system include directories or predefined macros, they usually don't.

Checkpoint

If the command is missing, install the cross-compiler package through your normal process. Don't work around it with the native cpp, that selects the host target instead.

2. Expand a small source file

cpp reads an input and writes preprocessed text. -E is accepted by this driver and makes the intent explicit. Start with standard input so the example changes no files:

$ printf '#define MESSAGE "AArch64"\nMESSAGE\n' |
  aarch64-linux-gnu-cpp-13 -E -P -
"AArch64"

-P strips the #line linemarkers a later compiler normally wants, which makes a quick look easier to read. Leave it out when another tool downstream needs accurate source locations.

For a real file, send the output somewhere named on purpose:

$ aarch64-linux-gnu-cpp-13 -E -I ./include src/main.c > /tmp/main.i
$ test -s /tmp/main.i && echo "preprocessed output exists"
preprocessed output exists

That never touches src/main.c. Treat the preprocessed output as an intermediate: it can be far bigger than the input and can leak implementation details from system headers.

3. Select macros without editing source

Use -D to supply a macro and -U to cancel one. Options run left to right, so the last one wins:

$ printf '#if FEATURE_LEVEL >= 2\nselected\n#else\nold\n#endif\n' |
  aarch64-linux-gnu-cpp-13 -E -P -DFEATURE_LEVEL=2 -
selected
$ printf '#if defined(TRACE)\ntrace\n#endif\n' |
  aarch64-linux-gnu-cpp-13 -E -P -DTRACE -UTRACE -
  • Quote function-like definitions. Parentheses and shell metacharacters mean something to the shell too: -D'PAIR(a,b)=((a)+(b))' is one argument.
  • Keep configuration in the build, not the shell. Put it in the build command or an included header rather than trusting a developer's ambient environment variables.

Checkpoint

If a conditional branch surprises you, dump the active macro set against an empty input:

$ aarch64-linux-gnu-cpp-13 -dM - < /dev/null | grep -E '^#define __aarch64__|^#define __GNUC__ '
#define __GNUC__ 13
#define __aarch64__ 1

That list is target and version specific. Never hard-code its order or assume every GCC installation defines the same implementation macros.

4. Make header lookup visible

Header failures are usually search-path failures in disguise. Add -H to print every header used, indented by nesting depth:

$ printf '#include <stdint.h>\n' |
  aarch64-linux-gnu-cpp-13 -E -H - 2>&1 | sed -n '1,2p'
. /usr/lib/gcc-cross/aarch64-linux-gnu/13/include/stdint.h
.. /usr/aarch64-linux-gnu/include/stdint.h

Exact paths and indentation depend on your installed packages, so trust your own output over this example. For project headers, -I DIR is searched before standard system directories; -iquote DIR applies only to #include "file.h"; -isystem DIR marks a directory as a system header location. Prefer -isystem for vendor headers instead of pretending they're project headers.

Tip

Don't add a broad current-directory include path just to silence a missing header. It can make an unintended local file win over the target's real header. Fix the include path or the source reference, then rerun the -H check.

5. Generate dependencies for Make

Use -MMD when the build should track project headers but skip system ones. Pair it with -MF for the dependency file and -MT for the target name:

$ aarch64-linux-gnu-cpp-13 -MMD -MF /tmp/main.d -MT build/main.o src/main.c > /tmp/main.i
$ sed -n '1,3p' /tmp/main.d
build/main.o: src/main.c include/project-config.h

That rule assumes src/main.c includes include/project-config.h; yours will differ. The output is a Make rule, not preprocessed C, so keep it in a separate .d file. -MM is the equivalent dependency mode that runs alongside normal preprocessor output; pick whichever spelling matches the rest of your build and check the generated rule.

This example only writes under /tmp. In a real build, use a private build directory and make sure a failed command can't overwrite a known-good dependency file: dependency files decide what gets rebuilt, so review a new one before it feeds into automation.

Common traps

A missing header is a non-zero preprocessor result. Reproducing it is quick:

$ printf '#include "does-not-exist.h"\n' |
  aarch64-linux-gnu-cpp-13 -E - > /dev/null
<stdin>:1:10: fatal error: does-not-exist.h: No such file or directory
compilation terminated.
  • Missing header: the diagnostic names the include and its location. Add the right directory with -I, or fix the spelling. Don't reach for -nostdinc as a generic fix, it disables every standard system include directory and makes you supply all of them yourself.
  • Treating cpp as a general text tool: it's a C-family preprocessor. It interprets comments, character constants, directives and macro tokens, so running it over a Makefile can strip significant hard tabs, and stray apostrophes can get parsed as C syntax. Use a language-specific preprocessor or a real text tool for non-C input.

Nothing here needs elevated privilege, a service restart or a persistent configuration change: the cross preprocessor just reads input and included files and prints text. Recovery is simply deleting temporary outputs like /tmp/main.i and /tmp/main.d once you're done with them.

Done means

  • Version confirmed: aarch64-linux-gnu-cpp-13 reports GCC 13.3.0 for the aarch64-linux-gnu target.
  • Expansion works: you can expand source with -E and decide when -P linemarkers help or hinder.
  • Macros verified: you can check command-line macro selection with -D, -U and -dM.
  • Header order visible: you can trace which file got picked with -H instead of guessing.
  • Dependencies separated: generated rules stay out of the preprocessed output.
  • Failures diagnosed: you can tell a missing include path from a misuse of cpp itself.