C++ Name Mangling in GCC
Posted by binglongx on March 17, 2025
Introduction
When you link a C/C++ program, the linker has to resolve symbols referring to functions or variables provided in other modules.
The symbols are names (see also Symbols and C++ Linkages). Here we are more intersted in function names.
- In the C world, there is no function overloading, so each function will have unique name.
- In C++, functions can be overloaded, and they can be differentiated by different parameter types (but not return type). C++ also has namespaces. Return type is ignored.
For linker to find the correct function, symbol therefore needs to encode both function name and parameter types in C++. Function name may nest in namespaces as well.
C++ uses mangling to encode the information and can result in funny-looking symbol names. Sometimes, you may see link or run-time link errors like unresolved symbols with weird names. They are often just mangled function names. They can be absurdly long if templates are involved.
Parts of Function Name
A general C++ function looks like:
return_type [namespaces]function_name[template_arguments](parameters)[qualifiers]
- return_type: return type of function. Mangling does NOT care return_type.
- namespaces: optional, if the function lives in a namespace, or as a class method.
- For mangling purpose, a class method is a namespaced function. Obviously compiler-generated call site already inserted a
thisparameter as first function parameter when seeing non-static method prototype at compile time.
- For mangling purpose, a class method is a namespaced function. Obviously compiler-generated call site already inserted a
- function_name: obvious. Note that there are special functions like operators, constructors etc.
- template_arguments: if the function is an instantiation of a function template, the template arguments exist.
- parameters: the list of parameter types.
voidis silently added if there is no parameters. - qualifiers: optional. If function is a class method, this differentiates constant, volatile or reference properties of the object the method works on.
Mangling
Mangling of a C++ function works as follows in GCC Itanium ABI.
High Level
In simplified fashion, at high level:
- return_type: ignored
_Z: all mangled C++ name starts with _Z.- if it has namespaces:
N: marks start of nesting- Each namespace in the list is then encoded as a name or type_name
- function_name
E: end of name
- else local scope:
Z: marks start of local scope nesting (function local objects, lambda etc)- encode local entities
E: end of name
- else: just encode the function name.
- template argument list:
Ifor <- types are encoded as type_names
Efor >
- paramters: types are encoded as list of type_names
- qualifiers: special abbreviations for constness, volatile and references etc.
Name
Some examples of namespace or class or function name:
- Regular name: number_of_characters name_string
- number_of_characters is needed to know when the name ends.
- E.g.
3fooforfoo.
- Special names/expressions, e.g.:
C1: constructorD1: destructorlt:operator <cl:operator ()
- Substitutions, e.g.:
St: for::std::
Type
Types can appear in various parts of mangled symbol name, e.g. function parameter list, template argument list, and namespaces. Some examples:
- built-in types. E.g.:
vforvoidbforbooliforintjforunsigned intfforfloatdfordouble
- Type modifiers, e.g.:
P<type>for<type>*R<type>for<type>&O<type>for<type>&&
- class: encode the class as name
- template parameters: encode as template argument list.
- Substitutions: short names for often used types, e.g.:
Sa: for::std::allocatorSb: for::std::basic_stringSs: for::std::stringSi: for::std::istreamSo: for::std::ostreamSd: for::std::iostream
Qualifier
Examples (see also type modifiers above):
K:constR:&O:&&
Examples
Here are some examples using Apple Clang 17.0. Note that it uses an extra leading _ for names.
You can use the following commands to try on macOS:
gcc test.cpp
nm a.out
c++filt
gcc builds the C++ program and produces an executable, by default named as a.out.
nm prints the symbols in the executable.
Feed c++filt with the mangled name, and it prints demangled name.
You could also demangler.com to demangle a name online.
C functions
No mangling. Use extern "C" if compiled with C++ compiler.
void foo(bool, int); // _foo
void bar(bool, int*); // _bar
C++ overloads
The parameter difference causes mangled name to differ.
float foo(bool, int); // foo(bool, int) : __Z3foobi
float foo(bool, int*); // foo(bool, int*) : __Z3foobPi
Namespace / class scope functions
namespace foo{ void func(int); } // foo::func(int) : __ZN3foo4funcEi
struct bar{ void func(int); }; // bar::func(int) : __ZN3bar4funcEi
namespace std {
struct __exception_ptr {
struct exception_ptr {
void _M_addref() {} // std::__exception_ptr::exception_ptr::_M_addref()
// __ZNSt15__exception_ptr13exception_ptr9_M_addrefEv
};
};
}
Note that St substitutes ::std::.
Local scope lambdas
C++ lambda creates an unnamed class that has an operator() method (const by default). That method is a function potentially having big chunk of machine code, therefore needs a symbol to link. So the symbol for the closure is also mangled.
Note that the symbol is for the operator() method of the unnamed class of the lambda.
int foo(int i) {
auto lambda1 = [](bool b) { // foo(int)::$_0::operator()(bool) const
return b? 42 : 0; // __ZZ3fooiENK3$_0clEb
};
auto lambda2 = [&i](int a) { // foo(int)::$_1::operator()(int) const
return a + i; // __ZZ3fooiENK3$_1clEi
};
return lambda1(i>0) + lambda2(22);
}
The mangled names show that lambda classes are named as $_0, $_1 etc in the local scope foo(int), i.e., Z3fooiE. The symbol refers to the class’s constant call operator $_1::operator() const like K3$_0cl.
Complex Example
Try to demangle this:
__ZNO2ns3FooINSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEiE8getMagicILb1EEEid
It looks scary.
The code generating this symbol is as follows:
#include <string>
namespace ns {
template<class T, class U>
struct Foo {
template<bool FLAG>
int getMagic(double v) && {
return 42 + FLAG + int(v);
}
};
}
int main() {
return ns::Foo<std::string, int>{}.getMagic<true>(3.14);
}
My interpretation is:
__ZNO2ns3FooINSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEiE8getMagicILb1EEEid
int ns::Foo<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, int>::getMagic<true>(double) &&
// __Z
int // i (move to bottom)
// N
// (O) moved from bottom
ns::Foo // 2ns3Foo
< // I
// N
std::__1:: // St3__1
basic_string // basic_string
< // I
char, // c
// N
std::__1:: // S1_
char_traits // 11char_traits
< // I
char // c
>, // E
// E
// N
std::__1:: // S1_
allocator // 9allocator
< // I
char // c
> // E
// E
>, // E
// E
int // i
>:: // E
getMagic // 8getMagic
< // I
// L
true // b1
// E
> // E
// E
// (i) (move from top)
(double) // d
&& // O (move to top)
Run-time Demangling
You can call __cxa_demangle() to demangle a name at run-time.
#include <iostream>
#include <cstdlib>
#include <cxxabi.h>
int main() {
const char* mangled_name
= "_ZNO2ns3FooINSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEiE8getMagicILb1EEEid";
int status;
char* demangled_name = abi::__cxa_demangle(mangled_name, nullptr, nullptr, &status);
if (status == 0 && demangled_name != nullptr) {
std::cout << "Demangled name: \n" << demangled_name << std::endl;
std::free(demangled_name); // Free the allocated memory
} else {
std::cerr << "Demangling failed with status: " << status << std::endl;
}
return 0;
}
The AI generated code above prints:
Demangled name:
int ns::Foo<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, int>::getMagic<true>(double) &&
Leave a comment