Name Mangling
Linkers were originally designed for C and Fortran, languages where function names are unique Identifiers. C++, however, introduces features that break this assumption:
- Overloading:
void print(int)andvoid print(float)share the same name. - Namespaces:
std::sortandboost::sortshare the same name. - Templates:
vector<int>andvector<float>generate distinct code for the same class template.
To enforce uniqueness in the global symbol table, C++ compilers perform Name Mangling (also Called Decoration). They encode the function signature (Scope, Name, Arguments) into a strict ASCII String.
Understanding mangling is required to decipher linker errors (“Undefined reference to _ZN3foo3barEi”) and to design interoperability interfaces (FFI).
1. Why Name Mangling Is Necessary
Section titled “1. Why Name Mangling Is Necessary”Proof: Overloading Requires Encoding Beyond the Name
Section titled “Proof: Overloading Requires Encoding Beyond the Name”Consider two functions in C++:
void process(int x);void process(double x);The linker operates on a flat symbol table where each entry is a (name, address) pair. Both Functions are named process. Without additional encoding, the linker cannot distinguish them.
More formally, the C++ language permits function overloading [N4950 S8.4.1]: two functions in the Same scope may have the same name if their parameter-type-lists are different. Since the linker’s Symbol table is a global flat map keyed by string (the symbol name), the compiler must encode the Parameter types into the name to produce unique keys.
The encoding must be deterministic (the same function always produces the same mangled name) and injective (two functions with different signatures produce different mangled names). This is Guaranteed by the ABI specification for each platform.
What Gets Mangled
Section titled “What Gets Mangled”Per the Itanium C++ ABI, the following entities are mangled:
- Top-level functions (including member functions, static member functions).
- Global variables with external or module linkage.
- Template instantiations (both function and class templates).
- Virtual table entries (
_ZTV``_ZTIfor RTTI). - Typeinfo structures for
typeidsupport. - Guard variables for static local initialization (
_ZGV).
Local variables, static variables with internal linkage, and entities in unnamed namespaces are not mangled (they do not appear in the symbol table).
2. The Itanium C++ ABI (GCC / Clang)
Section titled “2. The Itanium C++ ABI (GCC / Clang)”The Itanium C++ ABI is the standard mangling scheme used by GCC, Clang, and the majority of the Unix ecosystem (Linux, macOS, BSD, Android). It is designed to be parseable and logical.
Structure
Section titled “Structure”A mangled name generally follows this pattern: _Z + [N for Nested] + [Name Length][Name]... + [E for End] + [Argument Types]
Encoding Logic
Section titled “Encoding Logic”Consider the function:
namespace net { class Socket { void connect(int port, bool ssl); };}Mangled Output: _ZN3net6Socket7connectEib
_Z: Prefix marking a mangled symbol.N: Start of nested scope (Namespace/Class).3net: Identifier “net” (3 characters).6Socket: Identifier “Socket” (6 characters).7connect: Identifier “connect” (7 characters).E: End of nested scope.i: First argument typeint.b: Second argument typebool.
Substitution Compression
Section titled “Substitution Compression”A critical optimization in the Itanium ABI is substitution. When a name appears multiple times In a mangled symbol, subsequent occurrences are replaced with a reference (S_``S0_``S1_ Etc.). This prevents mangled names from growing exponentially for deeply nested types.
Consider:
void foo(std::vector<std::string>, std::vector<std::string>);Without substitution, std::vector<std::string> would be encoded twice in full. With Substitution, the second occurrence is replaced by S_:
_Z3fooSt6vectorINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEESaIS5_ES5_The S5_ refers to the sixth substitution candidate (numbered starting from 0), which is std::vector<std::__cxx11::basic_string<char, ...>>.
Reading and Parsing Mangled Names (Itanium)
Section titled “Reading and Parsing Mangled Names (Itanium)”The Itanium ABI defines a complete grammar for mangled names. Key type encodings:
| Source Type | Mangled | Notes |
|---|---|---|
void | v | |
bool | b | |
int | i | |
unsigned int | j | |
long long | x | |
float | f | |
double | d | |
char const* | PKc | P = pointer, K = const |
std::string | NSt6basic_stringIcSt11char_traitsIcESaIcEEE | Full qualified name |
int& | Ri | R = lvalue reference |
int&& | Oi | O = rvalue reference |
int const& | RKi | |
... (variadic) | z | |
template<int N> | IiE | I starts template args, E ends |
Parsing Example: std::vector<int>::push_back(int const&)
Section titled “Parsing Example: std::vector<int>::push_back(int const&)”Step by step through _ZNSt6vectorIiSaIiEE9push_backERKi:
_Z— mangled symbol prefixN— begin nested nameSt—std::namespace (special encoding)6vector— “vector” (6 chars)IiSaIiEE— template args:int``std::allocator<int>9push_back— “push_back” (9 chars)E— end nested nameRKi—int const&(return type not encoded for non-template functions)
Result: std::vector<int, std::allocator<int>>::push_back(int const&)
Template Argument Encoding
Section titled “Template Argument Encoding”Template arguments are encoded within I...E delimiters. Each argument is encoded according to its Type:
- Type arguments: Encoded using the normal type encoding rules.
- Non-type arguments (integers): Encoded as
LiVALUEE(literal, value, end). - Template template arguments: Encoded as
IT_NAMEEwhereT_NAMEis the template name.
template<typename T, int N>void process(T array[N]);
// process<int, 10> mangles the template args as:// IiLi10EE (int, literal 10)Template Instantiation Mangling
Section titled “Template Instantiation Mangling”Template instantiations produce some of the longest mangled names. Each template argument is fully Encoded, recursively:
template<typename T>void process(std::map<std::string, std::vector<T>>);An instantiation like process<int>(...) produces a deeply nested mangling because std::map And std::vector each carry their full template argument lists. For complex types, mangled names Can exceed 1000 characters.
# View the longest mangled names in a binarynm -C ./app | awk '{ print length, $0 }' | sort -rn | head -20:::note Return Types The Itanium ABI generally does not encode the return type of a function, as C++ does not support overloading based solely on return type. Exception: Template functions and auto return type deduction may trigger return type encoding. :::
3. The Microsoft ABI (MSVC)
Section titled “3. The Microsoft ABI (MSVC)”The Microsoft Visual C++ compiler uses a proprietary mangling scheme. It is significantly more Verbose and includes information not found in Itanium (such as Access Control levels and Calling Conventions).
Structure
Section titled “Structure”The pattern generally begins with ? and relies on special characters (@``$) as delimiters.
Consider the same function:
namespace net { class Socket { void connect(int port, bool ssl); };}Mangled Output (Approximate): ?connect@Socket@net@@QEAAXH_N@Z
?: Start of mangled symbol.connect: Function name.@: Delimiter.Socket@net: Scope (reversed order: Class, then Namespace).@@: End of scope list.QEAA: Access qualifiers (public/private) and calling convention (e.g.,__cdecl``__stdcall).X: Return type (void). Note: MSVC encodes return types.H: First argumentint._N: Second argumentbool.@Z: End of signature.
Key MSVC Mangling Differences from Itanium
Section titled “Key MSVC Mangling Differences from Itanium”| Aspect | Itanium (GCC/Clang) | MSVC |
|---|---|---|
| Prefix | _Z | ? |
| Return type | Not encoded () | Always encoded |
| Access control | Not encoded | Encoded (public, private, protected) |
| Calling conv. | Not encoded | Encoded (A = cdecl, G = stdcall) |
| Scope order | Outermost to innermost | Innermost to outermost (reversed) |
| Namespace std | St | ?A or std@@ |
| Substitutions | S_``S0_Etc. | Not used (names encoded in full) |
4. Disabling Mangling (extern "C")
Section titled “4. Disabling Mangling (extern "C")”To allow C code, Rust, Python (ctypes), or Java (JNI) to call a C++ function, you must disable Mangling. This forces the linker to use the “C” representation (the bare function name).
extern "C" void my_api_func(int x);- Mangled:
my_api_func(or_my_api_funcon some systems). - Restriction:
extern "C"functions cannot be overloaded or templated.
How extern "C" Affects Mangling
Section titled “How extern "C" Affects Mangling”Per [N4950 S10.5], an extern "C" linkage specification causes the entity to use C language Linkage. The practical effect on mangling:
- Functions: The function name is used without encoding parameter types. The symbol in the object file is the function name (with a possible platform-specific prefix like
_on Windows). - Variables: Similarly unmangled.
- No overloading: Since the mangled name does not encode types, two
extern "C"functions with the same name but different parameters would produce the same symbol, which the linker would flag as a duplicate definition.
Architectural Best Practice: The API Boundary
Section titled “Architectural Best Practice: The API Boundary”When designing a shared library (.dll/.so) for general consumption, wrap the interface in extern "C" block to provide a stable ABI.
#ifdef __cplusplusextern "C" {#endif
MYLIB_API void initialize_system(); MYLIB_API int process_data(void* ptr);
#ifdef __cplusplus}#endifextern "C" as a Mangling Escape Hatch
Section titled “extern "C" as a Mangling Escape Hatch”A common pattern is to use extern "C" as an opaque function pointer type when interfacing with C APIs that require callbacks:
// C library expects a function pointer with C linkageextern "C" typedef void (*CallbackFunc)(int);
// C++ function with C linkage for the callbackextern "C" void my_callback(int value) { // Can use C++ features inside std::vector<int> v; v.push_back(value);}
// Register with C APIc_library_register_callback(my_callback);:::caution Mixing function pointers with different linkage is undefined behavior [N4950 S10.5 p7]. A Function pointer of type extern "C" and one of type C++ linkage are different types, even if they Have the same parameter and return types. Passing one where the other is expected is UB. :::
5. Inspection and Demangling Tools
Section titled “5. Inspection and Demangling Tools”You rarely decode mangled names manually. Use the toolchain utilities to translate them.
Demangling with c++filt
Section titled “Demangling with c++filt”c++filt is part of GNU Binutils (or LLVM). It accepts a mangled name and prints the C++ signature.
$ c++filt _ZN3net6Socket7connectEibnet::Socket::connect(int, bool)Demangling with llvm-cxxfilt
Section titled “Demangling with llvm-cxxfilt”llvm-cxxfilt is the LLVM equivalent. It supports the same Itanium mangling and can also demangle Rust symbols:
$ llvm-cxxfilt _ZN3net6Socket7connectEibnet::Socket::connect(int, bool)
# Pipe nm output through c++filt for readable output$ nm ./app | c++filtInspecting Binaries (nm)
Section titled “Inspecting Binaries (nm)”The nm tool accepts a --demangle (or -C) flag.
$ nm -C ./app0000000000001149 T net::Socket::connect(int, bool)Demangling with undname
Section titled “Demangling with undname”Visual Studio provides undname.exe.
> undname ?connect@Socket@net@@QEAAXH_N@Zvoid __cdecl net::Socket::connect(int,bool)Inspecting Binaries (dumpbin)
Section titled “Inspecting Binaries (dumpbin)”dumpbin requires explicit flags to show symbols.
> dumpbin /SYMBOLS app.objNote: dumpbin output is often raw; piping it to a text file for analysis is recommended.
6. Name Mangling and Linker Errors
Section titled “6. Name Mangling and Linker Errors”When the linker reports Undefined reference to ...Look closely at the mangled name (or the Demangled output). The mangling encodes the exact signature, making it possible to diagnose Mismatches.
Mismatched Signatures
Section titled “Mismatched Signatures”If the linker reports:
undefined reference to `net::Socket::connect(int, bool)'But your code declares:
void connect(int port); // Missing bool parameterThe mangled names will differ (_ZN3net6Socket7connectEib vs _ZN3net6Socket7connectEi), and the Linker will correctly report the missing symbol.
Dual-ABI Mismatch (GCC)
Section titled “Dual-ABI Mismatch (GCC)”GCC 5 introduced a new ABI for std::string (using std::__cxx11::basic_string). If you link code Compiled with GCC 4 against a library compiled with GCC 5+, the mangled names for std::string Parameters will differ:
undefined reference to `foo(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)'Fix: Recompile all code with the same compiler version, or define _GLIBCXX_USE_CXX11_ABI=0 to Use the old ABI.
Missing extern "C"
Section titled “Missing extern "C"”If a C library exports init but your C++ code declares extern void init(); without extern "C" The compiler mangles the reference:
undefined reference to `_Z4initv'But the library exports the unmangled symbol init. The fix is extern "C" void init();.
Decode Example: Step-by-Step
Section titled “Decode Example: Step-by-Step”Given the linker error:
undefined reference to `_ZNK5Utils8parseArgERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE'`Step-by-step demangling:
_Z— mangled prefixN— nested nameK— const qualifier (this function is const-qualified)5Utils— namespaceUtils8parseArg— functionparseArgE— end nested nameR— referenceK— constNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE—const std::__cxx11::string&
Result: Utils::parseArg(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&) const
The K after N indicates this is a const member function. If your declaration lacks constThe Mangled name will differ, causing the linker error.
7. RTTI and type_info Name Mangling
Section titled “7. RTTI and type_info Name Mangling”RTTI (typeid``dynamic_cast) relies on mangled names internally. The std::type_info::name() Member function returns a compiler-specific, mangled representation of the type name:
#include <iostream>#include <typeinfo>
int main() { std::cout << typeid(int).name() << "\n"; // "i" (GCC/Clang) std::cout << typeid(std::string).name() << "\n"; // "NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE" return 0;}Important: The output of name() is not guaranteed to be human-readable or portable. For a Demangled name, use abi::__cxa_demangle (GCC/Clang) or typeid(T).name() with c++filt:
#include <iostream>#include <typeinfo>#include <cxxabi.h>#include <cstdlib>
std::string demangle(const char* mangled) { int status = 0; char* demangled = abi::__cxa_demangle(mangled, nullptr, nullptr, &status); std::string result(demangled ? demangled : mangled); std::free(demangled); return result;}
int main() { std::cout << demangle(typeid(std::string).name()) << "\n"; // Output: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > return 0;}Mangling in Debug Builds
Section titled “Mangling in Debug Builds”In debug builds (-g``-O0), the compiler emits additional DWARF debug information (.debug_info Sections) that contains the demangled names. However, the symbol table still contains mangled names. The DWARF info allows debuggers (GDB, LLDB) to map mangled symbols back to their source-level names.
Some compilers emit a __func__ or __PRETTY_FUNCTION__ string literal that contains the unmangled Signature. This is independent of the mangling scheme and is available even in release builds:
void foo(int x) { std::cout << __PRETTY_FUNCTION__ << "\n"; // Output: "void foo(int)"}8. Architectural Implications
Section titled “8. Architectural Implications”Binary Size (Templates)
Section titled “Binary Size (Templates)”Heavy template usage results in massive symbol names. A std::map<std::string, std::vector<MyCustomClass>> might result in a mangled string That is 400+ characters long.
- Impact: This bloats the string table of the binary, increasing executable size on disk.
- Mitigation:
extern templateprevents instantiating the same symbols in every object file.
// In header:extern template class std::vector<MyClass>;
// In one .cpp file:template class std::vector<MyClass>; // Explicit instantiationABI Incompatibility
Section titled “ABI Incompatibility”Because Itanium and MSVC use fundamentally different encoding logic, it is impossible to link an Object file compiled with Clang (GNU CLI) on Windows against a library compiled with MSVC, unless Using C-linkage (extern "C"). Clang-CL exists specifically to mimic the MSVC mangling scheme to Allow compatibility.
DSO Symbol Visibility
Section titled “DSO Symbol Visibility”On ELF platforms, symbols are exported from shared libraries by default. This means all mangled C++ Symbols are visible in the dynamic symbol table, increasing binary size and load time. Use -fvisibility=hidden and explicit __attribute__((visibility("default"))) to control which symbols Are exported:
# CMake: hide all symbols by defaultset(CMAKE_CXX_VISIBILITY_PRESET hidden)set(CMAKE_VISIBILITY_INLINES_HIDDEN ON)Name Length Limits
Section titled “Name Length Limits”The Itanium ABI does not impose a hard limit on mangled name length. However, some toolchain Components may have practical limits:
- ELF symbol table: No hard limit, but extremely long names increase
.strtabsize. - MSVC COFF: Internal limits may cause truncation for names exceeding ~4096 characters.
nmoutput: Line wrapping may make long names hard to read. Usenm -Cfor demangled output.
Common Pitfalls
Section titled “Common Pitfalls”- Forgetting
extern "C"for C callbacks: C++ function pointers have different mangling than C function pointers. Passing a C++ function to a C API callback withoutextern "C"causes linker errors or undefined behavior at runtime. - Mismatched
constin signatures:void foo(int)andvoid foo(const int)produce different mangled names in some contexts (member functions), causing unexpected undefined references. - Dual-ABI mismatch: Mixing GCC 4.x and GCC 5+ object files causes
std::__cxx11symbol mismatches. Always recompile everything with the same compiler. - Trusting
type_info::name(): The output is implementation-defined. Useabi::__cxa_demanglefor portable, human-readable type names. - Assuming ABI compatibility between Itanium and MSVC: Object files compiled with different ABI schemes cannot be linked. Use
extern "C"for cross-compiler interop. - Inline functions in headers without
inline: Defining a non-inline function in a header included by multiple TUs causes duplicate symbol errors because each TU produces a mangled symbol with the same name (but in different COMDAT groups if the linker supports it).
9. Mangling and Inline Namespaces
Section titled “9. Mangling and Inline Namespaces”Inline namespaces [N4950 S9.8.2] affect mangling because they contribute to the qualified name. An Inline namespace is encoded as part of the nested name:
namespace V1 { inline namespace Detail { struct Config { int value; }; }}The mangled name for V1::Config is _ZN2V16ConfigE (the inline namespace Detail does not appear In the mangled name for most lookup contexts). However, the full mangled name for V1::Detail::Config would include Detail. The exact behavior depends on whether the lookup Resolves through the inline namespace or directly.
Inline namespaces are commonly used for versioning ABI-compatible changes in library headers:
namespace lib { inline namespace v2 { void process(int x); } // v1::process(int) still accessible as lib::process(int) for backward compatibility}10. Mangling and Lambdas
Section titled “10. Mangling and Lambdas”Lambda closures produce unnamed types with compiler-generated names. These names are mangled Uniquely using a counter based on the lexical scope:
auto f = [](int x) { return x * 2; };The closure type is mangled as something like _ZZ1fvENK3$_0clEi (lambda in function f0th Lambda, operator() taking int). The exact encoding is compiler-specific but follows the general Pattern of including the enclosing function name and a lambda index.
This is relevant when debugging: if a lambda appears in a linker error (unlikely but possible if the Lambda captures something that requires a global symbol), the mangled name will include the Compiler-generated type name, which is unreadable without demangling.
11. Mangling and Concepts (C++20)
Section titled “11. Mangling and Concepts (C++20)”Concepts [N4950 S7.5] do not produce mangled names in the symbol table because they are compile-time Constraints, not runtime entities. However, concept names appear in constraint mangling for Constrained templates.
When a template has a constrained requires clause, the constraint is not encoded in the mangled Name. Two function templates with the same name and parameters but different constraints produce the Same mangled name, which would be an ODR violation if both were defined:
template<typename T> requires std::integral<T>void process(T x);
template<typename T> requires std::floating_point<T>void process(T x); // Same mangled name as above: _Z7processIiEvT_ (for int)This is not a practical problem because the compiler rejects the second definition as a redefinition At compile time. The standard prohibits defining two templates with the same name and parameter list But different constraints [N4950 S13.7.3 p6].
12. Mangling and noexcept Specifications
Section titled “12. Mangling and noexcept Specifications”Per the Itanium ABI, noexcept is part of the function type and does affect mangling in C++17 And later. A noexcept function and a potentially-throwing function with the same name and Parameters produce different mangled names:
void foo(int x); // _Z3fooivoid bar(int x) noexcept; // _Z3barNi (note the 'N' suffix for noexcept)This can cause subtle linker errors when a function is declared noexcept in one TU but not in Another. The mangled names differ, and the linker reports an undefined reference. The fix is to Ensure the noexcept specification is consistent across all declarations.
:::caution In C++17, noexcept became part of the type system. This means function pointer types Are different if their noexcept specification differs. A void(*)(int) and a void(*)(int) noexcept are different types and cannot be implicitly converted.
13. Practical Mangling Debugging Workflow
Section titled “13. Practical Mangling Debugging Workflow”When encountering an obscure linker error involving a mangled name, follow this systematic workflow:
- Copy the mangled name from the linker error output.
- Demangle it using
c++filtorllvm-cxxfilt:Terminal window echo '_ZNK5Utils8parseArgERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE' | c++filt - Identify the namespace, class, and function from the demangled name.
- Search the source code for the function declaration. Check:
- Is the function declared in the TU that references it?
- Is the correct
extern "C"linkage specified (if applicable)? - Are the parameter types an exact match (including
const``&``&&)? - Is the
noexceptspecification consistent?
- Check the library/object file that should provide the symbol:
Terminal window nm -C libutils.a | grep "Utils::parseArg" - If the symbol exists but the linker cannot find it, check link order (libraries after object files) and visibility (
-fvisibility=hidden).
14. Mangling Reference: Complete Type Encoding Table
Section titled “14. Mangling Reference: Complete Type Encoding Table”The following table provides a comprehensive reference for Itanium ABI type encodings:
| Source Type | Mangled | Notes |
|---|---|---|
void | v | |
bool | b | |
char | c | |
signed char | a | |
unsigned char | h | |
short | s | |
unsigned short | t | |
int | i | |
unsigned int | j | |
long | l | |
unsigned long | m | |
long long | x | |
unsigned long long | y | |
__int128 | n | |
float | f | |
double | d | |
long double | e | |
float _Complex | Cf | C99 complex |
double _Complex | Cd | C99 complex |
T* | PT | P = pointer |
T& | RT | R = lvalue reference |
T&& | OT | O = rvalue reference |
T const | KT | K = const |
T volatile | VT | V = volatile |
T const volatile | VK | |
T[] | AT_ | Array of unknown bound |
T[N] | AT_N | Array of N elements |
This table covers the most common encodings. The full specification is in the Itanium C++ ABI Document, section 5.1.5.
15. Mangling and Structured Bindings (C++17)
Section titled “15. Mangling and Structured Bindings (C++17)”Structured bindings [N4950 S9.7] do not introduce new mangled names. The compiler decomposes the Structured binding into individual references to the underlying object’s members or tuple elements. These references are local to the scope and do not appear in the symbol table.
16. Mangling and Coroutines (C++20)
Section titled “16. Mangling and Coroutines (C++20)”Coroutine-related symbols are mangled with special prefixes:
_ZSt16coro_resume_pv—std::coro_resume(void*)_ZSt17coro_destroy_pv—std::coro_destroy(void*)_ZSt12coro_done_pv—std::coro_done(void*)
The coroutine frame itself is a compiler-generated struct with a mangled name based on the coroutine Function’s signature. The frame type is internal to the compiler and does not appear in the public Symbol table.
See Also
Section titled “See Also”Summary
Section titled “Summary”This topic covers the essential concepts and techniques related to name mangling, including key principles and practical applications.
Key concepts include:
- core concepts and definitions
- key principles and frameworks
- practical applications
- common techniques and methods
- evaluation and critical analysis
A thorough understanding of these concepts, combined with regular practice and review, is essential for mastery of this topic.
Worked Examples
Section titled “Worked Examples”Worked examples demonstrating the application of key concepts are covered in the detailed sub-pages linked above.
:::