Demangle C++ Symbols with c++filt on Linux
You will turn compiler-generated C++ symbol names into readable function names, while preserving the surrounding punctuation in assembler-like input. The examples use the installed GNU Binutils 2.42 commands on Ubuntu, including the c++filt, aarch64-linux-gnu-c++filt and x86_64-linux-gnu-c++filt names.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about ten minutes. You need a shell and one of the binutils packages that provides c++filt. This is a read-only workflow: it does not alter binaries, source files or compiler settings, and it normally needs no elevated privileges.
Checkpoint
Stop after step 3 if you only need to decode one symbol. Continue to step 4 for source-like text or step 5 when you need to control the presentation.
1. Check the installed command
Confirm the executable and package version before relying on an option in a script. These commands only inspect the local installation:
$ command -v c++filt
/usr/bin/c++filt
$ c++filt --version
GNU c++filt (GNU Binutils for Ubuntu) 2.42
$ dpkg-query -W -f='${Package} ${Version}\n' binutils-common:amd64
binutils-common 2.42-4ubuntu2.10
The two target-prefixed aliases are also installed in this environment. They report the same Binutils release:
$ aarch64-linux-gnu-c++filt --version | head -1
GNU c++filt (GNU Binutils for Ubuntu) 2.42
$ x86_64-linux-gnu-c++filt --version | head -1
GNU c++filt (GNU Binutils for Ubuntu) 2.42
Use the unprefixed command for ordinary host symbols. Choose a target-prefixed alias when the symbol came from a toolchain for that target and you want the corresponding executable name to be explicit. The three installed manpages describe the same interface here.
2. Decode one complete symbol
Pass a mangled name as one shell argument. The name below is a small Itanium C++ ABI example for a function called f with no parameters:
$ c++filt -n _Z1fv
f()
The -n option means --no-strip-underscore. It tells this installation not to remove an initial underscore before attempting to demangling. That makes the intent visible and avoids a target-dependent default. The long form is easier to audit in a script:
$ c++filt --no-strip-underscore _ZN3Foo3barEi
Foo::bar(int)
A name that is not recognised is normally echoed rather than translated. That makes a mixed stream usable, but it also means that unchanged output is not proof that the input was a valid symbol. Check the input spelling and the compiler's mangling format when a name stays unchanged.
3. Keep punctuation outside the symbol
Command-line arguments are treated as complete names. Do not attach a comma, semicolon or other assembler punctuation to the argument:
$ c++filt -n '_Z1fv,'
_Z1fv,
When no symbol arguments are supplied, c++filt reads standard input and looks for potential symbol words inside the text. This is the right mode for a line copied from assembler output:
$ printf '%s\n' '.type _Z1fv, @function' | c++filt -n
.type f(), @function
Notice that the comma remains after the demangled name. The input mode can replace a recognised word while leaving surrounding text alone. The distinction is a common failure trap: a symbol copied from a listing may look correct but still contain punctuation that makes it invalid as a command-line argument.
Checkpoint
If a standalone command works but a copied source line does not, move the line to standard input instead of trying random flags.
4. Remove parameter types when names are enough
Function signatures are useful while debugging overload resolution, but noisy in a compact report. Use -p or --no-params to omit parameter types:
$ c++filt -p _Z3fooi
foo
Without that option, the same symbol includes its parameter type:
$ c++filt -n _Z3fooi
foo(int)
This only changes displayed detail. It does not merge overloads into one executable symbol or change the input. Keep the full form when two overloads must remain distinguishable.
5. Select a mangling format only when needed
Automatic selection is the default. If you know the producer's format, -s or --format= can select one of the formats documented by this installed manpage, including gnu, gnu-v3, java and gnat. For a normal modern GNU C++ symbol, the default is usually the least surprising choice:
$ c++filt --format=gnu-v3 _Z1fv
f()
Do not select a format just because the output looks unfamiliar. First check whether the symbol was truncated, copied with punctuation, or produced by another compiler. A forced format can make a valid name look undecodable when automatic detection would have been more appropriate.
--types asks the program to attempt type demangling as well as function-name demangling. It is disabled by default because ordinary short words can be mistaken for encoded types. Add it only when you know the input contains mangled type encodings and you have checked the resulting output.
6. Keep recursion protection enabled
The demangler accepts nested encodings. This installed release limits recursion to 2048 levels by default, which helps prevent a hostile or malformed name from exhausting the process stack. Keep that default for data you did not generate yourself.
-r and --no-recurse-limit disable the protection. The manual warns that stack exhaustion is then possible. Only test that mode with a controlled input, in a disposable process, when a genuinely complex symbol cannot be decoded otherwise. It is not a routine troubleshooting step and does not need root privileges.
Use -R or --recurse-limit to make the protected default explicit in a script:
$ printf '%s\n' _Z1fv | c++filt --recurse-limit
f()
7. Diagnose an unchanged or incomplete result
Work through these checks in order:
- Run
c++filt --versionand confirm which installation is being called. - Pass one complete symbol with
--no-strip-underscore, without trailing punctuation. - Pipe the original line through standard input if it contains assembler punctuation or surrounding text.
- Check whether the symbol came from a different compiler or language, then choose
--format=FORMATonly with evidence. - Keep recursion protection enabled unless a controlled, complex input specifically requires another setting.
There is no recovery operation because these commands do not modify their input. If you redirected output to a file and the result is wrong, delete or replace only that generated file after checking its path. Avoid using > over a useful report until you have confirmed the destination name; write to a new file first when the output matters.
Done means
- You confirmed the installed Binutils version and selected the intended executable.
- A complete mangled symbol produced the expected readable C++ name.
- You know when command-line mode differs from standard-input mode.
- You used
--no-paramsonly when removing parameter types was useful. - You left recursion protection enabled for untrusted or unfamiliar input.
- No source, binary, service or persistent system setting was changed.