C++ ABIs on x86-64 and x86, Itanium and Microsoft
C++ adds rules of its own on top of the C calling convention, for the names the linker sees, the tables behind virtual functions and the way an exception finds its catch. Two sets of rules cover nearly all x86 code, and objects compiled under one do not link with objects compiled under the other.
The C ABIs of the other pages say how a function receives its
arguments, but not what a C++ program adds: the hidden this, the
names of overloaded functions, virtual tables, pointers to members,
run-time type information and exceptions. Those rules make up a C++
ABI, and on x86 there are two:
| C++ ABI | Used by |
|---|---|
| Itanium C++ ABI | GCC; Clang on Linux, the BSDs, macOS and for MinGW-w64 |
| Microsoft C++ ABI | MSVC, and Clang for Windows targets with the MSVC ABI, including clang-cl |
The Itanium C++ ABI was written for Intel’s Itanium, but most of it is independent of the processor. GCC’s C++ has been based on it since GCC 3.2, and other compilers implement it too, notably on GNU/Linux and the BSDs. The Microsoft C++ ABI has no specification of that kind: Microsoft documents parts of it, such as decorated names and the forms of member pointers, and Clang implements it for its Windows targets. Both sit on the C convention of their platform, so MinGW-w64 code uses the Microsoft x64 registers with the Itanium C++ ABI. All listings on this page come from Clang, which compiles for both.
this
A non-static member function takes the object’s address as a hidden
argument. The Itanium C++ ABI passes it as the first argument, so on
x86-64 it arrives in RDI and on i386 in the first stack slot. Microsoft
x64 passes it first too, in RCX. On 32-bit Windows, member functions
use their own convention, __thiscall:
this in ECX and the other arguments on the stack, which the callee
removes.
Counter::add in each ABI: this in RDI, RCX, at 4(%esp) and in ECX; the 32-bit Windows version pops its argument with retl $4.cxx-this.cpp
// A member function: this is an argument that the source does not
// write.
struct Counter {
int n;
int add(int k);
};
int Counter::add(int k)
{
return n += k;
}
Clang 23.1.2 x86_64-linux-gnu
AT&T syntax clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -S cxx-this.cpp
_ZN7Counter3addEi:
movl %esi, %eax
addl (%rdi), %eax
movl %eax, (%rdi)
retqIntel syntax clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -masm=intel -S cxx-this.cpp
_ZN7Counter3addEi:
mov eax, esi
add eax, dword ptr [rdi]
mov dword ptr [rdi], eax
retClang 23.1.2 x86_64-pc-windows-msvc
AT&T syntax clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -S cxx-this.cpp
"?add@Counter@@QEAAHH@Z":
movl %edx, %eax
addl (%rcx), %eax
movl %eax, (%rcx)
retqIntel syntax clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -masm=intel -S cxx-this.cpp
"?add@Counter@@QEAAHH@Z":
mov eax, edx
add eax, dword ptr [rcx]
mov dword ptr [rcx], eax
retClang 23.1.2 i686-linux-gnu
AT&T syntax clang --target=i686-linux-gnu -march=i686 -O2 -fno-asynchronous-unwind-tables -S cxx-this.cpp
_ZN7Counter3addEi:
movl 4(%esp), %ecx
movl (%ecx), %eax
addl 8(%esp), %eax
movl %eax, (%ecx)
retlIntel syntax clang --target=i686-linux-gnu -march=i686 -O2 -fno-asynchronous-unwind-tables -masm=intel -S cxx-this.cpp
_ZN7Counter3addEi:
mov ecx, dword ptr [esp + 4]
mov eax, dword ptr [ecx]
add eax, dword ptr [esp + 8]
mov dword ptr [ecx], eax
retClang 23.1.2 i686-pc-windows-msvc
AT&T syntax clang --target=i686-pc-windows-msvc -march=pentium4 -O2 -fno-asynchronous-unwind-tables -S cxx-this.cpp
"?add@Counter@@QAEHH@Z":
movl (%ecx), %eax
addl 4(%esp), %eax
movl %eax, (%ecx)
retl $4Intel syntax clang --target=i686-pc-windows-msvc -march=pentium4 -O2 -fno-asynchronous-unwind-tables -masm=intel -S cxx-this.cpp
"?add@Counter@@QAEHH@Z":
mov eax, dword ptr [ecx]
add eax, dword ptr [esp + 4]
mov dword ptr [ecx], eax
ret 4Name mangling
C++ allows several functions of one name, in different namespaces and classes and with different parameters, so the name the linker sees encodes all of that. The two ABIs encode it differently, and a name from one never matches a name from the other.
_Z (with one more underscore on macOS, as for all C names there): N3geo4distE is geo::dist, RK a reference to const, and S_, S2_ refer back to parts already written. Microsoft names start with ?, with the name, then the scopes, and end in @Z.cxx-mangling.cpp
// Overloads, a namespace and a template: each call names a different
// function, and the mangled names tell them apart.
namespace geo {
struct Point { int x, y; };
int dist(const Point &a, const Point &b);
int dist(int a, int b);
template <typename T> T scale(T v, int k);
}
int use(const geo::Point &p)
{
return geo::dist(p, p) + geo::dist(1, 2) + geo::scale<long>(3, 4);
}
Clang 23.1.2 x86_64-linux-gnu
AT&T syntax clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -S cxx-mangling.cpp
_Z3useRKN3geo5PointE:
pushq %rbp
pushq %rbx
pushq %rax
movq %rdi, %rsi
callq _ZN3geo4distERKNS_5PointES2_@PLT
movl %eax, %ebx
movl $1, %edi
movl $2, %esi
callq _ZN3geo4distEii@PLT
movl %eax, %ebp
addl %ebx, %ebp
movl $3, %edi
movl $4, %esi
callq _ZN3geo5scaleIlEET_S1_i@PLT
addl %ebp, %eax
addq $8, %rsp
popq %rbx
popq %rbp
retqIntel syntax clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -masm=intel -S cxx-mangling.cpp
_Z3useRKN3geo5PointE:
push rbp
push rbx
push rax
mov rsi, rdi
call _ZN3geo4distERKNS_5PointES2_@PLT
mov ebx, eax
mov edi, 1
mov esi, 2
call _ZN3geo4distEii@PLT
mov ebp, eax
add ebp, ebx
mov edi, 3
mov esi, 4
call _ZN3geo5scaleIlEET_S1_i@PLT
add eax, ebp
add rsp, 8
pop rbx
pop rbp
retClang 23.1.2 x86_64-apple-macos11
AT&T syntax clang --target=x86_64-apple-macos11 -O2 -fno-asynchronous-unwind-tables -S cxx-mangling.cpp
__Z3useRKN3geo5PointE:
pushq %rbp
movq %rsp, %rbp
pushq %r14
pushq %rbx
movq %rdi, %rsi
callq __ZN3geo4distERKNS_5PointES2_
movl %eax, %ebx
movl $1, %edi
movl $2, %esi
callq __ZN3geo4distEii
movl %eax, %r14d
addl %ebx, %r14d
movl $3, %edi
movl $4, %esi
callq __ZN3geo5scaleIlEET_S1_i
addl %r14d, %eax
popq %rbx
popq %r14
popq %rbp
retqIntel syntax clang --target=x86_64-apple-macos11 -O2 -fno-asynchronous-unwind-tables -masm=intel -S cxx-mangling.cpp
__Z3useRKN3geo5PointE:
push rbp
mov rbp, rsp
push r14
push rbx
mov rsi, rdi
call __ZN3geo4distERKNS_5PointES2_
mov ebx, eax
mov edi, 1
mov esi, 2
call __ZN3geo4distEii
mov r14d, eax
add r14d, ebx
mov edi, 3
mov esi, 4
call __ZN3geo5scaleIlEET_S1_i
add eax, r14d
pop rbx
pop r14
pop rbp
retClang 23.1.2 x86_64-pc-windows-msvc
AT&T syntax clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -S cxx-mangling.cpp
"?use@@YAHAEBUPoint@geo@@@Z":
pushq %rsi
pushq %rdi
subq $40, %rsp
movq %rcx, %rdx
callq "?dist@geo@@YAHAEBUPoint@1@0@Z"
movl %eax, %esi
movl $1, %ecx
movl $2, %edx
callq "?dist@geo@@YAHHH@Z"
movl %eax, %edi
addl %esi, %edi
movl $3, %ecx
movl $4, %edx
callq "??$scale@J@geo@@YAJJH@Z"
addl %edi, %eax
addq $40, %rsp
popq %rdi
popq %rsi
retqIntel syntax clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -masm=intel -S cxx-mangling.cpp
"?use@@YAHAEBUPoint@geo@@@Z":
push rsi
push rdi
sub rsp, 40
mov rdx, rcx
call "?dist@geo@@YAHAEBUPoint@1@0@Z"
mov esi, eax
mov ecx, 1
mov edx, 2
call "?dist@geo@@YAHHH@Z"
mov edi, eax
add edi, esi
mov ecx, 3
mov edx, 4
call "??$scale@J@geo@@YAJJH@Z"
add eax, edi
add rsp, 40
pop rdi
pop rsi
retThe Itanium name of an ordinary function encodes its parameters but
not its return type; only the names of template functions include it,
like T_ in _ZN3geo5scaleIlEET_S1_i, where IlE is the template
argument long. Microsoft’s names encode the return type and the
calling convention of every function. Tools turn the names back into
declarations: c++filt or llvm-cxxfilt for Itanium names, and
Microsoft’s undname for its own.
Virtual tables
An object of a class with virtual functions starts with a pointer to its class’s table of virtual functions, and a virtual call loads the function from it. The two ABIs lay the tables out differently:
| Itanium C++ ABI | Microsoft C++ ABI | |
|---|---|---|
| Before the functions | the offset to the top of the object, then the type information | a pointer to the complete object locator, which leads to the type information |
| The object points to | the first function, after those two entries | the first function |
| Virtual destructor | two entries: the complete object destructor and the deleting destructor | one entry: the vector deleting destructor, which takes flags |
| Emitted in | the object file that defines the key function, if the class has one | every object file that needs it, merged by the linker |
_ZTV6Square holds 0, the type information _ZTI6Square, the two destructors, area and sides; make_square stores _ZTV6Square+16, and area_of calls through 16(%rax), the third function. Microsoft: ??_7Square@@6B@ is defined 8 bytes into a block whose first word points to the complete object locator ??_R4Square@@6B@; area is the second entry, at 8(%rax), after the deleting destructor.cxx-vtable.cpp
// A class with virtual functions, one of them defined here, an object
// of it made here, and a virtual call.
struct Shape {
virtual ~Shape();
virtual int area() const = 0;
virtual int sides() const { return 0; }
};
struct Square : Shape {
int side = 1;
int area() const override;
int sides() const override { return 4; }
};
int Square::area() const
{
return side * side;
}
Shape *make_square()
{
return new Square;
}
int area_of(const Shape &s)
{
return s.area();
}
Clang 23.1.2 x86_64-linux-gnu
AT&T syntax clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -S cxx-vtable.cpp
_ZNK6Square4areaEv:
movl 8(%rdi), %eax
imull %eax, %eax
retq
_Z11make_squarev:
pushq %rax
movl $16, %edi
callq _Znwm@PLT
leaq _ZTV6Square+16(%rip), %rcx
movq %rcx, (%rax)
movl $1, 8(%rax)
popq %rcx
retq
_Z7area_ofRK5Shape:
movq (%rdi), %rax
jmpq *16(%rax)
_ZN6SquareD0Ev:
pushq %rbx
movq %rdi, %rbx
callq _ZN5ShapeD2Ev@PLT
movl $16, %esi
movq %rbx, %rdi
popq %rbx
jmp _ZdlPvm@PLT
_ZNK6Square5sidesEv:
movl $4, %eax
retq
_ZTV6Square:
.quad 0
.quad _ZTI6Square
.quad _ZN5ShapeD2Ev
.quad _ZN6SquareD0Ev
.quad _ZNK6Square4areaEv
.quad _ZNK6Square5sidesEv
_ZTI6Square:
.quad _ZTVN10__cxxabiv120__si_class_type_infoE+16
.quad _ZTS6Square
.quad _ZTI5Shape
_ZTS6Square:
.asciz "6Square"Intel syntax clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -masm=intel -S cxx-vtable.cpp
_ZNK6Square4areaEv:
mov eax, dword ptr [rdi + 8]
imul eax, eax
ret
_Z11make_squarev:
push rax
mov edi, 16
call _Znwm@PLT
lea rcx, [rip + _ZTV6Square+16]
mov qword ptr [rax], rcx
mov dword ptr [rax + 8], 1
pop rcx
ret
_Z7area_ofRK5Shape:
mov rax, qword ptr [rdi]
jmp qword ptr [rax + 16]
_ZN6SquareD0Ev:
push rbx
mov rbx, rdi
call _ZN5ShapeD2Ev@PLT
mov esi, 16
mov rdi, rbx
pop rbx
jmp _ZdlPvm@PLT
_ZNK6Square5sidesEv:
mov eax, 4
ret
_ZTV6Square:
.quad 0
.quad _ZTI6Square
.quad _ZN5ShapeD2Ev
.quad _ZN6SquareD0Ev
.quad _ZNK6Square4areaEv
.quad _ZNK6Square5sidesEv
_ZTI6Square:
.quad _ZTVN10__cxxabiv120__si_class_type_infoE+16
.quad _ZTS6Square
.quad _ZTI5Shape
_ZTS6Square:
.asciz "6Square"Clang 23.1.2 x86_64-pc-windows-msvc
AT&T syntax clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -S cxx-vtable.cpp
"?area@Square@@UEBAHXZ":
movl 8(%rcx), %eax
imull %eax, %eax
retq
"?make_square@@YAPEAUShape@@XZ":
subq $40, %rsp
movl $16, %ecx
callq "??2@YAPEAX_K@Z"
leaq "??_7Square@@6B@"(%rip), %rcx
movq %rcx, (%rax)
movl $1, 8(%rax)
addq $40, %rsp
retq
"?area_of@@YAHAEBUShape@@@Z":
movq (%rcx), %rax
rex64 jmpq *8(%rax)
"?sides@Square@@UEBAHXZ":
movl $4, %eax
retq
"?sides@Shape@@UEBAHXZ":
xorl %eax, %eax
retq
"??_GShape@@UEAAPEAXI@Z":
ud2
"??_GSquare@@UEAAPEAXI@Z":
pushq %rsi
pushq %rdi
subq $40, %rsp
movl %edx, %edi
movq %rcx, %rsi
callq "??1Shape@@UEAA@XZ"
testb $1, %dil
je .LBB6_2
movl $16, %edx
movq %rsi, %rcx
callq "??3@YAXPEAX_K@Z"
.LBB6_2:
movq %rsi, %rax
addq $40, %rsp
popq %rdi
popq %rsi
retq
.L__unnamed_1:
.quad "??_R4Square@@6B@"
.quad "??_ESquare@@UEAAPEAXI@Z"
.quad "?area@Square@@UEBAHXZ"
.quad "?sides@Square@@UEBAHXZ"
"??_R4Square@@6B@":
.long 1
.long 0
.long 0
.long "??_R0?AUSquare@@@8"@IMGREL
.long "??_R3Square@@8"@IMGREL
.long "??_R4Square@@6B@"@IMGREL
"??_R0?AUSquare@@@8":
.quad "??_7type_info@@6B@"
.quad 0
.asciz ".?AUSquare@@"
.zero 3
"??_R3Square@@8":
.long 0
.long 0
.long 2
.long "??_R2Square@@8"@IMGREL
"??_R2Square@@8":
.long "??_R1A@?0A@EA@Square@@8"@IMGREL
.long "??_R1A@?0A@EA@Shape@@8"@IMGREL
.long 0
"??_R1A@?0A@EA@Square@@8":
.long "??_R0?AUSquare@@@8"@IMGREL
.long 1
.long 0
.long 4294967295
.long 0
.long 64
.long "??_R3Square@@8"@IMGREL
"??_R1A@?0A@EA@Shape@@8":
.long "??_R0?AUShape@@@8"@IMGREL
.long 0
.long 0
.long 4294967295
.long 0
.long 64
.long "??_R3Shape@@8"@IMGREL
"??_R0?AUShape@@@8":
.quad "??_7type_info@@6B@"
.quad 0
.asciz ".?AUShape@@"
.zero 4
"??_R3Shape@@8":
.long 0
.long 0
.long 1
.long "??_R2Shape@@8"@IMGREL
"??_R2Shape@@8":
.long "??_R1A@?0A@EA@Shape@@8"@IMGREL
.long 0
"??_R4Shape@@6B@":
.long 1
.long 0
.long 0
.long "??_R0?AUShape@@@8"@IMGREL
.long "??_R3Shape@@8"@IMGREL
.long "??_R4Shape@@6B@"@IMGREL
"??_7Square@@6B@" = .L__unnamed_1+8
"??_ESquare@@UEAAPEAXI@Z" = "??_GSquare@@UEAAPEAXI@Z"Intel syntax clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -masm=intel -S cxx-vtable.cpp
"?area@Square@@UEBAHXZ":
mov eax, dword ptr [rcx + 8]
imul eax, eax
ret
"?make_square@@YAPEAUShape@@XZ":
sub rsp, 40
mov ecx, 16
call "??2@YAPEAX_K@Z"
lea rcx, [rip + "??_7Square@@6B@"]
mov qword ptr [rax], rcx
mov dword ptr [rax + 8], 1
add rsp, 40
ret
"?area_of@@YAHAEBUShape@@@Z":
mov rax, qword ptr [rcx]
rex64 jmp qword ptr [rax + 8]
"?sides@Square@@UEBAHXZ":
mov eax, 4
ret
"?sides@Shape@@UEBAHXZ":
xor eax, eax
ret
"??_GShape@@UEAAPEAXI@Z":
ud2
"??_GSquare@@UEAAPEAXI@Z":
push rsi
push rdi
sub rsp, 40
mov edi, edx
mov rsi, rcx
call "??1Shape@@UEAA@XZ"
test dil, 1
je .LBB6_2
mov edx, 16
mov rcx, rsi
call "??3@YAXPEAX_K@Z"
.LBB6_2:
mov rax, rsi
add rsp, 40
pop rdi
pop rsi
ret
.L__unnamed_1:
.quad "??_R4Square@@6B@"
.quad "??_ESquare@@UEAAPEAXI@Z"
.quad "?area@Square@@UEBAHXZ"
.quad "?sides@Square@@UEBAHXZ"
"??_R4Square@@6B@":
.long 1
.long 0
.long 0
.long "??_R0?AUSquare@@@8"@IMGREL
.long "??_R3Square@@8"@IMGREL
.long "??_R4Square@@6B@"@IMGREL
"??_R0?AUSquare@@@8":
.quad "??_7type_info@@6B@"
.quad 0
.asciz ".?AUSquare@@"
.zero 3
"??_R3Square@@8":
.long 0
.long 0
.long 2
.long "??_R2Square@@8"@IMGREL
"??_R2Square@@8":
.long "??_R1A@?0A@EA@Square@@8"@IMGREL
.long "??_R1A@?0A@EA@Shape@@8"@IMGREL
.long 0
"??_R1A@?0A@EA@Square@@8":
.long "??_R0?AUSquare@@@8"@IMGREL
.long 1
.long 0
.long 4294967295
.long 0
.long 64
.long "??_R3Square@@8"@IMGREL
"??_R1A@?0A@EA@Shape@@8":
.long "??_R0?AUShape@@@8"@IMGREL
.long 0
.long 0
.long 4294967295
.long 0
.long 64
.long "??_R3Shape@@8"@IMGREL
"??_R0?AUShape@@@8":
.quad "??_7type_info@@6B@"
.quad 0
.asciz ".?AUShape@@"
.zero 4
"??_R3Shape@@8":
.long 0
.long 0
.long 1
.long "??_R2Shape@@8"@IMGREL
"??_R2Shape@@8":
.long "??_R1A@?0A@EA@Shape@@8"@IMGREL
.long 0
"??_R4Shape@@6B@":
.long 1
.long 0
.long 0
.long "??_R0?AUShape@@@8"@IMGREL
.long "??_R3Shape@@8"@IMGREL
.long "??_R4Shape@@6B@"@IMGREL
"??_7Square@@6B@" = .L__unnamed_1+8
"??_ESquare@@UEAAPEAXI@Z" = "??_GSquare@@UEAAPEAXI@Z"The key function of a class, in the Itanium C++ ABI, is its first
virtual function that is neither pure nor inline where the class is
defined; here Square::area. The table goes into the object file that
defines it, once for the whole program. Microsoft’s vftable goes into
every object file that creates such an object, in a COMDAT section,
and the linker keeps one copy.
Member function pointers
A pointer to a member function must handle virtual functions, whose
address depends on the object, and classes with several bases, where
this must be adjusted. The Itanium C++ ABI always uses two words: the
function’s address, or 1 plus the offset of its entry in the virtual
table, and the adjustment of this. An odd first word marks a virtual
function, since the ABI relies on member functions starting at even
addresses.
Microsoft’s representation depends on the class’s inheritance: with single inheritance, a pointer to a member function is just the address of a function. For a virtual function that address is a thunk, which makes the virtual call. Multiple and virtual inheritance add fields, and a class that is not yet defined gets the largest form.
invoke adds the adjustment in RDX to this, tests bit 0 of the first word and, if it is set, loads the function from the table; pick returns the pair in RAX and RDX, 1 for size (entry 0, plus 1). Such pointers take 16 bytes, for any class. Microsoft: invoke just jumps to the pointer, pick returns ??_9Widget@@$BA@AA, a thunk that calls entry 0, and the pointer takes 8 bytes; for the undefined class Later, 24.cxx-member-pointer.cpp
// Pointers to member functions: calling one, taking one of a virtual
// and of a non-virtual function, and their sizes.
struct Widget {
int value;
int get();
virtual int size();
};
typedef int (Widget::*Method)();
int invoke(Widget *w, Method m)
{
return (w->*m)();
}
Method pick(bool virt)
{
return virt ? &Widget::size : &Widget::get;
}
struct Later;
int method_size = sizeof(Method);
int later_size = sizeof(int (Later::*)());
Clang 23.1.2 x86_64-linux-gnu
AT&T syntax clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -S cxx-member-pointer.cpp
_Z6invokeP6WidgetMS_FivE:
addq %rdx, %rdi
testb $1, %sil
je .LBB0_2
movq (%rdi), %rax
movq -1(%rax,%rsi), %rsi
.LBB0_2:
jmpq *%rsi
_Z4pickb:
movl $1, %eax
testl %edi, %edi
jne .LBB1_2
movq _ZN6Widget3getEv@GOTPCREL(%rip), %rax
.LBB1_2:
xorl %edx, %edx
retq
method_size:
.long 16
later_size:
.long 16Intel syntax clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -masm=intel -S cxx-member-pointer.cpp
_Z6invokeP6WidgetMS_FivE:
add rdi, rdx
test sil, 1
je .LBB0_2
mov rax, qword ptr [rdi]
mov rsi, qword ptr [rax + rsi - 1]
.LBB0_2:
jmp rsi
_Z4pickb:
mov eax, 1
test edi, edi
jne .LBB1_2
mov rax, qword ptr [rip + _ZN6Widget3getEv@GOTPCREL]
.LBB1_2:
xor edx, edx
ret
method_size:
.long 16
later_size:
.long 16Clang 23.1.2 x86_64-pc-windows-msvc
AT&T syntax clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -S cxx-member-pointer.cpp
"?invoke@@YAHPEAUWidget@@P81@EAAHXZ@Z":
rex64 jmpq *%rdx
"?pick@@YAP8Widget@@EAAHXZ_N@Z":
leaq "??_9Widget@@$BA@AA"(%rip), %rdx
leaq "?get@Widget@@QEAAHXZ"(%rip), %rax
testb %cl, %cl
cmovneq %rdx, %rax
retq
"??_9Widget@@$BA@AA":
movq (%rcx), %rax
movq (%rax), %rax
rex64 jmpq *%rax
"?method_size@@3HA":
.long 8
"?later_size@@3HA":
.long 24Intel syntax clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -masm=intel -S cxx-member-pointer.cpp
"?invoke@@YAHPEAUWidget@@P81@EAAHXZ@Z":
rex64 jmp rdx
"?pick@@YAP8Widget@@EAAHXZ_N@Z":
lea rdx, [rip + "??_9Widget@@$BA@AA"]
lea rax, [rip + "?get@Widget@@QEAAHXZ"]
test cl, cl
cmovne rax, rdx
ret
"??_9Widget@@$BA@AA":
mov rax, qword ptr [rcx]
mov rax, qword ptr [rax]
rex64 jmp rax
"?method_size@@3HA":
.long 8
"?later_size@@3HA":
.long 24Exceptions
In the Itanium C++ ABI, throw allocates the exception with
__cxa_allocate_exception and passes it to __cxa_throw with its type
information. __cxa_throw hands it to the unwind library of the
psABI, which walks the
frames and calls each function’s personality routine, for C++
__gxx_personality_v0. The routine reads the function’s table of call
sites and catch clauses, its language-specific data area or LSDA, and
decides whether the function catches the exception. A catch block calls
__cxa_begin_catch for the exception object, and __cxa_end_catch
when it is done. Nothing runs when the program enters a try block.
.cfi_personality and .cfi_lsda point the unwind information of safe_parse at the personality routine and at its LSDA: a call site from .Ltmp0 to .Ltmp1 that lands at .Ltmp2 with action 1, which catches the type _ZTIi, int. For MinGW-w64, Clang uses the same runtime functions and tables, but the exception travels through Windows’ unwinding: __gxx_personality_seh0 is the handler of the function’s .xdata.cxx-exceptions.cpp
// Throwing an int, and catching it around a call.
int parse(const char *s);
int safe_parse(const char *s)
{
try {
return parse(s);
} catch (int code) {
return -code;
}
}
void fail(int code)
{
throw code;
}
Clang 23.1.2 x86_64-linux-gnu
AT&T syntax clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -fasynchronous-unwind-tables -S cxx-exceptions.cpp
_Z10safe_parsePKc:
.Lfunc_begin0:
.cfi_startproc
.cfi_personality 155, DW.ref.__gxx_personality_v0
.cfi_lsda 27, .Lexception0
pushq %rbx
.cfi_def_cfa_offset 16
.cfi_offset %rbx, -16
.Ltmp0:
callq _Z5parsePKc@PLT
.Ltmp1:
popq %rbx
.cfi_def_cfa_offset 8
retq
.cfi_def_cfa_offset 16
.Ltmp2:
movq %rax, %rdi
callq __cxa_begin_catch@PLT
xorl %ebx, %ebx
subl (%rax), %ebx
callq __cxa_end_catch@PLT
movl %ebx, %eax
popq %rbx
.cfi_def_cfa_offset 8
retq
.Lfunc_end0:
.cfi_endproc
GCC_except_table0:
.Lexception0:
.byte 255
.byte 155
.uleb128 .Lttbase0-.Lttbaseref0
.Lttbaseref0:
.byte 1
.uleb128 .Lcst_end0-.Lcst_begin0
.Lcst_begin0:
.uleb128 .Ltmp0-.Lfunc_begin0
.uleb128 .Ltmp1-.Ltmp0
.uleb128 .Ltmp2-.Lfunc_begin0
.byte 1
.uleb128 .Ltmp1-.Lfunc_begin0
.uleb128 .Lfunc_end0-.Ltmp1
.byte 0
.byte 0
.Lcst_end0:
.byte 1
.byte 0
.Ltmp3:
.long .L_ZTIi.DW.stub-.Ltmp3
.Lttbase0:
_Z4faili:
.cfi_startproc
pushq %rbx
.cfi_def_cfa_offset 16
.cfi_offset %rbx, -16
movl %edi, %ebx
movl $4, %edi
callq __cxa_allocate_exception@PLT
movl %ebx, (%rax)
movq _ZTIi@GOTPCREL(%rip), %rsi
movq %rax, %rdi
xorl %edx, %edx
callq __cxa_throw@PLT
.cfi_endproc
.L_ZTIi.DW.stub:
.quad _ZTIi
DW.ref.__gxx_personality_v0:
.quad __gxx_personality_v0Intel syntax clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -fasynchronous-unwind-tables -masm=intel -S cxx-exceptions.cpp
_Z10safe_parsePKc:
.Lfunc_begin0:
.cfi_startproc
.cfi_personality 155, DW.ref.__gxx_personality_v0
.cfi_lsda 27, .Lexception0
push rbx
.cfi_def_cfa_offset 16
.cfi_offset rbx, -16
.Ltmp0:
call _Z5parsePKc@PLT
.Ltmp1:
pop rbx
.cfi_def_cfa_offset 8
ret
.cfi_def_cfa_offset 16
.Ltmp2:
mov rdi, rax
call __cxa_begin_catch@PLT
xor ebx, ebx
sub ebx, dword ptr [rax]
call __cxa_end_catch@PLT
mov eax, ebx
pop rbx
.cfi_def_cfa_offset 8
ret
.Lfunc_end0:
.cfi_endproc
GCC_except_table0:
.Lexception0:
.byte 255
.byte 155
.uleb128 .Lttbase0-.Lttbaseref0
.Lttbaseref0:
.byte 1
.uleb128 .Lcst_end0-.Lcst_begin0
.Lcst_begin0:
.uleb128 .Ltmp0-.Lfunc_begin0
.uleb128 .Ltmp1-.Ltmp0
.uleb128 .Ltmp2-.Lfunc_begin0
.byte 1
.uleb128 .Ltmp1-.Lfunc_begin0
.uleb128 .Lfunc_end0-.Ltmp1
.byte 0
.byte 0
.Lcst_end0:
.byte 1
.byte 0
.Ltmp3:
.long .L_ZTIi.DW.stub-.Ltmp3
.Lttbase0:
_Z4faili:
.cfi_startproc
push rbx
.cfi_def_cfa_offset 16
.cfi_offset rbx, -16
mov ebx, edi
mov edi, 4
call __cxa_allocate_exception@PLT
mov dword ptr [rax], ebx
mov rsi, qword ptr [rip + _ZTIi@GOTPCREL]
mov rdi, rax
xor edx, edx
call __cxa_throw@PLT
.cfi_endproc
.L_ZTIi.DW.stub:
.quad _ZTIi
DW.ref.__gxx_personality_v0:
.quad __gxx_personality_v0Clang 23.1.2 x86_64-w64-windows-gnu
AT&T syntax clang --target=x86_64-w64-windows-gnu -O2 -fno-asynchronous-unwind-tables -fasynchronous-unwind-tables -S cxx-exceptions.cpp
_Z10safe_parsePKc:
.Lfunc_begin0:
.seh_proc _Z10safe_parsePKc
.seh_handler __gxx_personality_seh0, @unwind, @except
pushq %rsi
.seh_pushreg %rsi
subq $32, %rsp
.seh_stackalloc 32
.seh_endprologue
.Ltmp0:
callq _Z5parsePKc
nop
.Ltmp1:
.LBB0_2:
.seh_startepilogue
addq $32, %rsp
popq %rsi
.seh_endepilogue
retq
.Ltmp2:
movq %rax, %rcx
callq __cxa_begin_catch
xorl %esi, %esi
subl (%rax), %esi
callq __cxa_end_catch
movl %esi, %eax
jmp .LBB0_2
.Lfunc_end0:
.seh_handlerdata
.seh_endproc
GCC_except_table0:
.Lexception0:
.byte 255
.byte 0
.uleb128 .Lttbase0-.Lttbaseref0
.Lttbaseref0:
.byte 1
.uleb128 .Lcst_end0-.Lcst_begin0
.Lcst_begin0:
.uleb128 .Ltmp0-.Lfunc_begin0
.uleb128 .Ltmp1-.Ltmp0
.uleb128 .Ltmp2-.Lfunc_begin0
.byte 1
.uleb128 .Ltmp1-.Lfunc_begin0
.uleb128 .Lfunc_end0-.Ltmp1
.byte 0
.byte 0
.Lcst_end0:
.byte 1
.byte 0
.quad _ZTIi
.Lttbase0:
_Z4faili:
.seh_proc _Z4faili
pushq %rsi
.seh_pushreg %rsi
subq $32, %rsp
.seh_stackalloc 32
.seh_endprologue
movl %ecx, %esi
movl $4, %ecx
callq __cxa_allocate_exception
movl %esi, (%rax)
movq .refptr._ZTIi(%rip), %rdx
movq %rax, %rcx
xorl %r8d, %r8d
callq __cxa_throw
int3
.seh_endproc
.refptr._ZTIi:
.quad _ZTIiIntel syntax clang --target=x86_64-w64-windows-gnu -O2 -fno-asynchronous-unwind-tables -fasynchronous-unwind-tables -masm=intel -S cxx-exceptions.cpp
_Z10safe_parsePKc:
.Lfunc_begin0:
.seh_proc _Z10safe_parsePKc
.seh_handler __gxx_personality_seh0, @unwind, @except
push rsi
.seh_pushreg rsi
sub rsp, 32
.seh_stackalloc 32
.seh_endprologue
.Ltmp0:
call _Z5parsePKc
nop
.Ltmp1:
.LBB0_2:
.seh_startepilogue
add rsp, 32
pop rsi
.seh_endepilogue
ret
.Ltmp2:
mov rcx, rax
call __cxa_begin_catch
xor esi, esi
sub esi, dword ptr [rax]
call __cxa_end_catch
mov eax, esi
jmp .LBB0_2
.Lfunc_end0:
.seh_handlerdata
.seh_endproc
GCC_except_table0:
.Lexception0:
.byte 255
.byte 0
.uleb128 .Lttbase0-.Lttbaseref0
.Lttbaseref0:
.byte 1
.uleb128 .Lcst_end0-.Lcst_begin0
.Lcst_begin0:
.uleb128 .Ltmp0-.Lfunc_begin0
.uleb128 .Ltmp1-.Ltmp0
.uleb128 .Ltmp2-.Lfunc_begin0
.byte 1
.uleb128 .Ltmp1-.Lfunc_begin0
.uleb128 .Lfunc_end0-.Ltmp1
.byte 0
.byte 0
.Lcst_end0:
.byte 1
.byte 0
.quad _ZTIi
.Lttbase0:
_Z4faili:
.seh_proc _Z4faili
push rsi
.seh_pushreg rsi
sub rsp, 32
.seh_stackalloc 32
.seh_endprologue
mov esi, ecx
mov ecx, 4
call __cxa_allocate_exception
mov dword ptr [rax], esi
mov rdx, qword ptr [rip + .refptr._ZTIi]
mov rcx, rax
xor r8d, r8d
call __cxa_throw
int3
.seh_endproc
.refptr._ZTIi:
.quad _ZTIiMicrosoft’s C++ exceptions are built on the structured exception
handling of Windows. throw calls _CxxThrowException with the
object and a description of its type and of the types it can be
caught as. On x64 the handler of a function with try,
__CxxFrameHandler3, reads tables that map the function’s code to
states and states to try blocks and their catch clauses, and each
catch block is compiled as a small function of its own. On 32-bit
Windows the same handler is registered in the
chain at FS:0 on
entry to the function.
fail passes _TI1H, which leads through _CTA1H and _CT??_R0H@84 to the type descriptor of int, ??_R0H@8. The tables of safe_parse start at $cppxdata$: the try block, its catch clause for ??_R0H@8 handled by the funclet ?catch$2@..., and the map from code addresses to states. The funclet returns the address where safe_parse continues, .LBB0_1.cxx-exceptions.cpp
// Throwing an int, and catching it around a call.
int parse(const char *s);
int safe_parse(const char *s)
{
try {
return parse(s);
} catch (int code) {
return -code;
}
}
void fail(int code)
{
throw code;
}
Clang 23.1.2 x86_64-pc-windows-msvc
AT&T syntax clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -fasynchronous-unwind-tables -S cxx-exceptions.cpp
"?safe_parse@@YAHPEBD@Z":
.Lfunc_begin0:
.seh_proc "?safe_parse@@YAHPEBD@Z"
.seh_handler __CxxFrameHandler3, @unwind, @except
pushq %rbp
.seh_pushreg %rbp
subq $64, %rsp
.seh_stackalloc 64
leaq 64(%rsp), %rbp
.seh_setframe %rbp, 64
.seh_endprologue
movq $-2, -16(%rbp)
.Ltmp0:
callq "?parse@@YAHPEBD@Z"
movl %eax, -20(%rbp)
.Ltmp1:
.LBB0_1:
movl -20(%rbp), %eax
.seh_startepilogue
addq $64, %rsp
popq %rbp
.seh_endepilogue
retq
.seh_handlerdata
.long "$cppxdata$?safe_parse@@YAHPEBD@Z"@IMGREL
.seh_endproc
"?catch$2@?0??safe_parse@@YAHPEBD@Z@4HA":
.seh_proc "?catch$2@?0??safe_parse@@YAHPEBD@Z@4HA"
.seh_handler __CxxFrameHandler3, @unwind, @except
movq %rdx, 16(%rsp)
pushq %rbp
.seh_pushreg %rbp
subq $32, %rsp
.seh_stackalloc 32
leaq 64(%rdx), %rbp
.seh_endprologue
xorl %eax, %eax
subl -4(%rbp), %eax
movl %eax, -20(%rbp)
leaq .LBB0_1(%rip), %rax
.seh_startepilogue
addq $32, %rsp
popq %rbp
.seh_endepilogue
retq
.seh_handlerdata
.long "$cppxdata$?safe_parse@@YAHPEBD@Z"@IMGREL
.seh_endproc
"$cppxdata$?safe_parse@@YAHPEBD@Z":
.long 429065506
.long 2
.long "$stateUnwindMap$?safe_parse@@YAHPEBD@Z"@IMGREL
.long 1
.long "$tryMap$?safe_parse@@YAHPEBD@Z"@IMGREL
.long 4
.long "$ip2state$?safe_parse@@YAHPEBD@Z"@IMGREL
.long 48
.long 0
.long 1
"$stateUnwindMap$?safe_parse@@YAHPEBD@Z":
.long -1
.long 0
.long -1
.long 0
"$tryMap$?safe_parse@@YAHPEBD@Z":
.long 0
.long 0
.long 1
.long 1
.long "$handlerMap$0$?safe_parse@@YAHPEBD@Z"@IMGREL
"$handlerMap$0$?safe_parse@@YAHPEBD@Z":
.long 0
.long "??_R0H@8"@IMGREL
.long 60
.long "?catch$2@?0??safe_parse@@YAHPEBD@Z@4HA"@IMGREL
.long 56
"$ip2state$?safe_parse@@YAHPEBD@Z":
.long .Lfunc_begin0@IMGREL
.long -1
.long .Ltmp0@IMGREL
.long 0
.long .Ltmp1@IMGREL
.long -1
.long "?catch$2@?0??safe_parse@@YAHPEBD@Z@4HA"@IMGREL
.long 1
"?fail@@YAXH@Z":
.seh_proc "?fail@@YAXH@Z"
subq $40, %rsp
.seh_stackalloc 40
.seh_endprologue
movl %ecx, 36(%rsp)
leaq _TI1H(%rip), %rdx
leaq 36(%rsp), %rcx
callq _CxxThrowException
int3
.seh_endproc
"??_R0H@8":
.quad "??_7type_info@@6B@"
.quad 0
.asciz ".H"
.zero 5
"_CT??_R0H@84":
.long 1
.long "??_R0H@8"@IMGREL
.long 0
.long 4294967295
.long 0
.long 4
.long 0
_CTA1H:
.long 1
.long "_CT??_R0H@84"@IMGREL
_TI1H:
.long 0
.long 0
.long 0
.long _CTA1H@IMGRELIntel syntax clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -fasynchronous-unwind-tables -masm=intel -S cxx-exceptions.cpp
"?safe_parse@@YAHPEBD@Z":
.Lfunc_begin0:
.seh_proc "?safe_parse@@YAHPEBD@Z"
.seh_handler __CxxFrameHandler3, @unwind, @except
push rbp
.seh_pushreg rbp
sub rsp, 64
.seh_stackalloc 64
lea rbp, [rsp + 64]
.seh_setframe rbp, 64
.seh_endprologue
mov qword ptr [rbp - 16], -2
.Ltmp0:
call "?parse@@YAHPEBD@Z"
mov dword ptr [rbp - 20], eax
.Ltmp1:
.LBB0_1:
mov eax, dword ptr [rbp - 20]
.seh_startepilogue
add rsp, 64
pop rbp
.seh_endepilogue
ret
.seh_handlerdata
.long "$cppxdata$?safe_parse@@YAHPEBD@Z"@IMGREL
.seh_endproc
"?catch$2@?0??safe_parse@@YAHPEBD@Z@4HA":
.seh_proc "?catch$2@?0??safe_parse@@YAHPEBD@Z@4HA"
.seh_handler __CxxFrameHandler3, @unwind, @except
mov qword ptr [rsp + 16], rdx
push rbp
.seh_pushreg rbp
sub rsp, 32
.seh_stackalloc 32
lea rbp, [rdx + 64]
.seh_endprologue
xor eax, eax
sub eax, dword ptr [rbp - 4]
mov dword ptr [rbp - 20], eax
lea rax, [rip + .LBB0_1]
.seh_startepilogue
add rsp, 32
pop rbp
.seh_endepilogue
ret
.seh_handlerdata
.long "$cppxdata$?safe_parse@@YAHPEBD@Z"@IMGREL
.seh_endproc
"$cppxdata$?safe_parse@@YAHPEBD@Z":
.long 429065506
.long 2
.long "$stateUnwindMap$?safe_parse@@YAHPEBD@Z"@IMGREL
.long 1
.long "$tryMap$?safe_parse@@YAHPEBD@Z"@IMGREL
.long 4
.long "$ip2state$?safe_parse@@YAHPEBD@Z"@IMGREL
.long 48
.long 0
.long 1
"$stateUnwindMap$?safe_parse@@YAHPEBD@Z":
.long -1
.long 0
.long -1
.long 0
"$tryMap$?safe_parse@@YAHPEBD@Z":
.long 0
.long 0
.long 1
.long 1
.long "$handlerMap$0$?safe_parse@@YAHPEBD@Z"@IMGREL
"$handlerMap$0$?safe_parse@@YAHPEBD@Z":
.long 0
.long "??_R0H@8"@IMGREL
.long 60
.long "?catch$2@?0??safe_parse@@YAHPEBD@Z@4HA"@IMGREL
.long 56
"$ip2state$?safe_parse@@YAHPEBD@Z":
.long .Lfunc_begin0@IMGREL
.long -1
.long .Ltmp0@IMGREL
.long 0
.long .Ltmp1@IMGREL
.long -1
.long "?catch$2@?0??safe_parse@@YAHPEBD@Z@4HA"@IMGREL
.long 1
"?fail@@YAXH@Z":
.seh_proc "?fail@@YAXH@Z"
sub rsp, 40
.seh_stackalloc 40
.seh_endprologue
mov dword ptr [rsp + 36], ecx
lea rdx, [rip + _TI1H]
lea rcx, [rsp + 36]
call _CxxThrowException
int3
.seh_endproc
"??_R0H@8":
.quad "??_7type_info@@6B@"
.quad 0
.asciz ".H"
.zero 5
"_CT??_R0H@84":
.long 1
.long "??_R0H@8"@IMGREL
.long 0
.long 4294967295
.long 0
.long 4
.long 0
_CTA1H:
.long 1
.long "_CT??_R0H@84"@IMGREL
_TI1H:
.long 0
.long 0
.long 0
.long _CTA1H@IMGRELMSVC’s /EH option chooses what catch (...) catches: with /EHa,
also the structured exceptions of the processor and the system, such
as access violations; with /EHsc, only C++ exceptions.
Summary
| Itanium C++ ABI | Microsoft C++ ABI | |
|---|---|---|
this | the first argument | the first argument; ECX on 32-bit x86 |
| Names | _Z...; no return type except for templates | ?...@Z; with return type and calling convention |
| Virtual table | offset to top and type information before the functions; key function | locator before the functions; COMDAT |
| Pointer to member function | always two words | one field to four, by inheritance |
| Exceptions | __cxa_throw, personality routine and LSDA | _CxxThrowException, __CxxFrameHandler3 and its tables |
Sources
- Itanium C++ ABI: commit
fae5e46c8614, 2026-01-20 - Itanium C++ ABI: Exception Handling: commit
fae5e46c8614, 2026-01-20 - GCC 14.2 manual: Binary Compatibility: as published on 2026-09-25
- System V Application Binary Interface, AMD64 Architecture Processor Supplement, version 1.0: commit
e1ce098331da, 2025-03-12 - Microsoft Learn: Decorated names: commit
f70d88cd5da7, 2026-09-24 - Microsoft Learn: __thiscall: commit
f70d88cd5da7, 2026-09-24 - Clang 23.1.2: clang/lib/CodeGen/MicrosoftCXXABI.cpp: commit
85ac56026243, 2026-09-20 - Microsoft Learn: Inheritance keywords: commit
f70d88cd5da7, 2026-09-24 - Microsoft Learn: /vmb, /vmg (Representation method): commit
f70d88cd5da7, 2026-09-24 - Microsoft Learn: /EH (Exception handling model): commit
f70d88cd5da7, 2026-09-24 - Microsoft Learn: x64 exception handling: commit
f70d88cd5da7, 2026-09-24 - LLVM 23.1.2: llvm/lib/Target/X86/X86WinEHState.cpp: commit
85ac56026243, 2026-09-20