Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 2

C++ Name Mangling & ABI

Recommended resources

Hello! Welcome back to our journey into the internals of C++.

In our last lesson, we surveyed the C++ build process and discovered that the compiler uses name mangling to create unique linker symbols for features like function overloading. We also saw how extern "C" acts as a bridge, allowing C++ code to link against C code by suppressing this mangling.

Today, we're going to dive much deeper into this topic. You'll move from knowing that name mangling exists to understanding how it works. We will analyze the structure of mangled names as defined by the Application Binary Interface (ABI), specifically the Itanium C++ ABI used by GCC and Clang on Linux. This lesson will solidify your understanding of how high-level C++ features are represented at the low level required for linking, a crucial concept for C++ systems programming.

The ABI: A Contract for Compilers

Before we dissect mangled names, it's essential to firmly grasp what an Application Binary Interface (ABI) is. While an API is a contract for a programmer (how to call a function in source code), an ABI is a contract for the compiler and linker (how to call a function in machine code). It defines the low-level, "under the hood" details.

To get a clear overview of the ABI and its importance, let's watch a few segments from a talk by Louis Dionne, a prominent C++ developer.

CppCon 2019: Louis Dionne “The C++ ABI From the Ground Up”

This clip from Louis Dionne's CppCon talk, “The C++ ABI From the Ground Up”, perfectly contrasts the ABI with an API and explains why ABI stability is so critical for software distribution.

Watch from 01:29 to 05:34. Focus on his analogy of the ABI being an 'API for machine code' and his explanation of the costs associated with breaking ABI compatibility, such as needing to recompile all dependent applications.

The ABI specifies many things, including:

  • How function arguments are passed (in registers vs. on the stack).
  • The memory layout of classes and structs.
  • How virtual functions are called (which we'll cover later).
  • And, crucially for this lesson, the scheme for name mangling.

Why C++ Needs a Mangling Scheme

As we saw in the previous lesson, C has a very simple ABI for function names: the symbol name is the same as the function name. This works because C does not allow two functions to have the same name in the same scope.

C++, on the other hand, supports features that require more information to be encoded into the symbol name:

  • Function Overloading: foo(int) and foo(double) must have different symbol names.
  • Namespaces: my_app::foo() and another_app::foo() must be distinguishable.
  • Class Methods: A member function MyClass::foo() needs to be differentiated from a global foo().
  • Templates: std::vector<int> and std::vector<double> are distinct types and their methods need unique symbols.

Name mangling is the solution. It's a set of rules for encoding this extra information into a string that the linker can use as a unique identifier.

Let's hear Louis Dionne explain this specific aspect.

CppCon 2019: Louis Dionne “The C++ ABI From the Ground Up”

In this next clip, Louis Dionne directly addresses why C++ needs name mangling and gives a great anecdote about how ABI constraints can even influence language design decisions.

Watch from 09:59 to 13:23. Pay close attention to how he explains that namespaces and function parameter types must be incorporated into the final symbol name.


Deconstructing a Mangled Name: The Itanium C++ ABI

While Microsoft's MSVC compiler has its own ABI, most non-Windows compilers, including GCC and Clang on Linux, have adopted the Itanium C++ ABI. Let's examine its mangling rules.

A great way to understand this is to read a detailed breakdown.

Name Mangling with Itanium ABI

This article, 'Name Mangling with Itanium ABI', provides a clear and concise explanation of the mangling rules. It will walk you through the structure of a mangled name, which we will then apply to a concrete example.

Please read the sections 'What and Why' and 'Itanium C++ ABI'. Focus on understanding the basic structure: the '_Z' prefix, the different encoding types (unscoped, nested), and how parameter types are represented.

Now, let's put that theory into practice. Consider this function in a namespace:

namespace my_app {
    void print(int i, char c) { /* ... */ }
}

According to the Itanium ABI, the mangled name for this function would be _ZN6my_app5printEic. Let's break that down:

  • _Z: The standard prefix for all mangled C++ symbols in the Itanium ABI.
  • N: Indicates a nested name (i.e., inside a namespace or class).
  • 6 my_app: The first part of the name is 6 characters long: "my_app".
  • 5 print: The second part of the name is 5 characters long: "print".
  • E: Marks the end of the nested name sequence.
  • i: The first parameter is an int.
  • c: The second parameter is a char.

As you can see, it's a deterministic encoding scheme. The linker doesn't see a function "print" in a namespace "my_app"; it just sees a unique global symbol named _ZN6my_app5printEic.

C++ Name Mangling Example
This table visually contrasts simple C++ function declarations with their Itanium ABI mangled names. You can now see how `_Z5myFunv` represents `myFun(void)` and `_Z5myFunic` represents `myFun(int, char)`, following the rules we just discussed.

Tools of the Trade: nm, readelf, and c++filt

Understanding the theory is good, but seeing it in action is better. On Linux, you have powerful command-line tools to inspect symbols in your compiled object files and executables.

  • nm: Lists symbols from object files.
  • readelf -s: A more powerful tool that displays the full symbol table of an ELF file (object file, shared library, or executable).
  • c++filt: A demangling tool. You give it a mangled name, and it gives you back the human-readable C++ signature.

Let's see how you'd use these. Imagine you have a file test.cpp with our my_app::print function.

  1. Compile to an object file:

    g++ -c test.cpp -o test.o
    
  2. Inspect the symbols:

    readelf -s test.o
    

    In the output, you would find a line containing our mangled name:
    ... FUNC GLOBAL DEFAULT ... _ZN6my_app5printEic

  3. Demangle the name:

    c++filt _ZN6my_app5printEic
    

    This would output:
    my_app::print(int, char)

This workflow is incredibly valuable for debugging complex linker errors. It allows you to see exactly what symbols your compiler is generating and what symbols the linker is looking for.


The Impact on Linking: A Practical Demonstration

Now that we have a deep understanding of mangling, the problem of linking C and C++ code becomes crystal clear. A C compiler produces simple, unmangled symbols. A C++ compiler produces complex, mangled symbols. The linker, seeing two different names, cannot match them.

The extern "C" specifier is the explicit instruction to the C++ compiler to "switch to the C ABI" for a given function, meaning it should produce a simple, unmangled symbol.

This video provides a perfect, step-by-step demonstration of the entire issue.

extern "C" In C++

The channel CppNuts offers a very practical walkthrough of the linker problem when mixing C and C++. It uses nm to show you the symbol names and demonstrates exactly why extern "C" is the solution.

Watch the video from the beginning up to 08:03. The author compiles a C file and a C++ file separately, shows the linker error, uses nm to inspect the symbol tables to diagnose the problem (mangled vs. unmangled names), and then uses extern "C" to fix it.

The common pattern for making a C header file compatible with both C and C++ compilers uses the __cplusplus macro, which is only defined by C++ compilers.

Using extern "C" for C and C++ Interoperability
This standard C header pattern uses `#ifdef __cplusplus` to conditionally apply `extern "C"`. When a C compiler includes this header, it ignores the `extern "C"` blocks. When a C++ compiler includes it, it applies C linkage to the function declarations, ensuring link compatibility.
Test your understanding!

Given the following C++ function:

namespace math_lib {
    class Calculator {
    public:
        double add(double a, double b);
    };
}

A simplified mangled name for the add method might look like this: _ZN8math_lib10Calculator3addEdd.
Based on what you've learned about the Itanium ABI mangling scheme, can you explain what each part of this mangled name represents?

Show answer

Let's break down _ZN8math_lib10Calculator3addEdd:

  • _Z: The standard prefix.
  • N: Indicates a nested name (the function is inside a namespace and a class).
  • 8 math_lib: The first scope is "math_lib" (8 characters).
  • 10 Calculator: The second scope is "Calculator" (10 characters).
  • 3 add: The function name is "add" (3 characters).
  • E: End of the name part.
  • d: The first parameter is a double.
  • d: The second parameter is a double.

The demangled signature would be math_lib::Calculator::add(double, double).


Conclusion

You have now dug deep into one of the most fundamental "under the hood" mechanisms that separates C++ from C. You've gone from knowing that name mangling exists to being able to analyze and even predict the structure of mangled names.

Key Takeaways:

  • The Application Binary Interface (ABI) is a low-level contract that defines how compiled code interacts, including calling conventions and name mangling rules.
  • C++'s name mangling is necessary to create unique linker symbols for features like function overloading and namespaces.
  • The Itanium C++ ABI, used by GCC and Clang, has a specific encoding scheme (_Z, N...E, type codes) that deterministically converts a C++ signature into a unique symbol.
  • Tools like readelf and c++filt allow you to inspect symbol tables and demangle names, which is invaluable for debugging linker issues.
  • extern "C" works by instructing the C++ compiler to use the C ABI for a function, producing a simple, unmangled symbol that can be linked against C code.

In our next lesson, we will continue our comparative analysis of C and C++. We'll investigate references, a feature unique to C++, and analyze how they are typically implemented at the assembly level compared to their close relatives, pointers.