Binglong's space

Random notes on computer, phone, life, anything

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 this parameter as first function parameter when seeing non-static method prototype at compile time.
  • 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. void is 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:
    • I for <
    • types are encoded as type_names
    • E for >
  • 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. 3foo for foo.
  • Special names/expressions, e.g.:
    • C1: constructor
    • D1: destructor
    • lt : 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.:
    • v for void
    • b for bool
    • i for int
    • j for unsigned int
    • f for float
    • d for double
  • 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::allocator
    • Sb: for ::std::basic_string
    • Ss: for ::std::string
    • Si: for ::std::istream
    • So: for ::std::ostream
    • Sd: for ::std::iostream

Qualifier

Examples (see also type modifiers above):

  • K : const
  • R : &
  • 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) &&

References

Leave a comment