x86-64 と x86 の C++ ABI: Itanium と Microsoft
C++ は C の呼び出し規約の上に独自の規則を加えます。リンカーが見る名前、仮想関数の背後にあるテーブル、例外が catch にたどり着く方法についての規則です。x86 のコードのほぼすべては 2 組の規則のどちらかに従い、一方でコンパイルしたオブジェクトは、もう一方でコンパイルしたオブジェクトとリンクできません。
ほかのページの C ABI は、関数が引数をどう受け取るかを定めますが、C++ プログラムが加えるもの、つまり隠れた this、オーバーロードされた関数の名前、仮想関数テーブル、メンバーへのポインター、実行時型情報 (run-time type information)、例外については定めていません。これらの規則が C++ ABI を構成し、x86 には 2 つの C++ ABI があります。
| C++ ABI | 使用するもの |
|---|---|
| Itanium C++ ABI | GCC、および Linux、BSD 系、macOS 上と MinGW-w64 向けの Clang |
| Microsoft C++ ABI | MSVC、および MSVC ABI の Windows ターゲット向けの Clang (clang-cl を含む) |
Itanium C++ ABI は Intel の Itanium 向けに書かれましたが、その大部分はプロセッサーに依存しません。GCC の C++ は GCC 3.2 以降これに基づいており、ほかのコンパイラーも、特に GNU/Linux と BSD 系でこれを実装しています。Microsoft C++ ABI にはそのような仕様書がありません。Microsoft は装飾名やメンバーポインターの形式などその一部を文書化しており、Clang は Windows ターゲット向けにこれを実装しています。どちらもそれぞれのプラットフォームの C の呼び出し規約の上に成り立っているため、MinGW-w64 のコードは Microsoft x64 のレジスターを Itanium C++ ABI と組み合わせて使います。このページのリストはすべて、両方の ABI 向けにコンパイルできる Clang によるものです。
this
非静的メンバー関数は、オブジェクトのアドレスを隠し引数として受け取ります。Itanium C++ ABI はこれを第 1 引数として渡すため、x86-64 では RDI に、i386 では最初のスタックスロットに届きます。Microsoft x64 もこれを第 1 引数として RCX で渡します。32 ビット Windows では、メンバー関数は独自の呼び出し規約 __thiscall を使います。this は ECX に入り、ほかの引数はスタックに置かれ、それを呼び出し先が取り除きます。
Counter::add。this は RDI、RCX、4(%esp)、ECX にあります。32 ビット Windows 版は 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 構文 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 構文 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 構文 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 構文 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 構文 clang --target=i686-linux-gnu -march=i686 -mtune=generic -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 構文 clang --target=i686-linux-gnu -march=i686 -mtune=generic -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 構文 clang --target=i686-pc-windows-msvc -march=pentium4 -mtune=generic -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 構文 clang --target=i686-pc-windows-msvc -march=pentium4 -mtune=generic -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 4名前修飾
C++ では、名前空間やクラスが異なる、あるいはパラメーターが異なる同じ名前の関数を複数定義できるため、リンカーが見る名前はそれらすべてをエンコードします。2 つの ABI はこの名前修飾 (name mangling) を異なる方法で行い、一方の名前がもう一方の名前と一致することはありません。
_Z で始まり (macOS では、すべての C の名前と同様にアンダースコアがもう 1 つ付きます)、N3geo4distE は geo::dist、RK は const への参照を表し、S_ と S2_ はすでに書かれた部分を参照します。Microsoft の名前は ? で始まり、名前、続いてスコープが並び、@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 構文 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 構文 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 構文 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 構文 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 構文 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 構文 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
ret通常の関数の Itanium の名前は、パラメーターをエンコードしますが戻り値の型はエンコードしません。戻り値の型を含むのはテンプレート関数の名前だけで、_ZN3geo5scaleIlEET_S1_i の T_ がそれにあたります。ここで IlE はテンプレート引数 long です。Microsoft の名前は、すべての関数について戻り値の型と呼び出し規約をエンコードします。ツールを使うと名前を宣言に戻せます。Itanium の名前には c++filt または llvm-cxxfilt、Microsoft の名前には Microsoft の undname を使います。
仮想関数テーブル
仮想関数を持つクラスのオブジェクトは、そのクラスの仮想関数テーブルへのポインターで始まり、仮想呼び出しは関数をそこから読み込みます。2 つの ABI ではテーブルのレイアウトが異なります。
| Itanium C++ ABI | Microsoft C++ ABI | |
|---|---|---|
| 関数の前 | オブジェクトの先頭までのオフセット、続いて型情報 | 型情報につながる完全オブジェクトロケーター (complete object locator) へのポインター |
| オブジェクトが指す先 | この 2 つのエントリの後にある最初の関数 | 最初の関数 |
| 仮想デストラクター | 2 つのエントリ (完全オブジェクトデストラクターと削除デストラクター) | 1 つのエントリ (フラグを受け取るベクトル削除デストラクター) |
| 出力先 | キー関数がある場合、それを定義するオブジェクトファイル | それを必要とするすべてのオブジェクトファイル (リンカーが 1 つにまとめる) |
_ZTV6Square は 0、型情報 _ZTI6Square、2 つのデストラクター、area、sides を保持します。make_square は _ZTV6Square+16 を格納し、area_of は 3 番目の関数である 16(%rax) を通じて呼び出します。Microsoft: ??_7Square@@6B@ は、最初のワードが完全オブジェクトロケーター ??_R4Square@@6B@ を指すブロックの、先頭から 8 バイトの位置に定義されています。area は削除デストラクターに続く 2 番目のエントリで、8(%rax) にあります。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 構文 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 構文 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 構文 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 構文 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"Itanium C++ ABI でのクラスのキー関数 (key function) は、クラスが定義される場所で純粋仮想でもインラインでもない最初の仮想関数で、ここでは Square::area です。テーブルはそれを定義するオブジェクトファイルに、プログラム全体で 1 回だけ置かれます。Microsoft の vftable は、そのようなオブジェクトを作成するすべてのオブジェクトファイルに COMDAT セクションとして置かれ、リンカーが 1 つだけ残します。
メンバー関数ポインター
メンバー関数へのポインターは、アドレスがオブジェクトに依存する仮想関数と、this の調整が必要になる複数の基底クラスを持つクラスを扱えなければなりません。Itanium C++ ABI は常に 2 ワードを使います。1 つ目は関数のアドレス、または仮想関数テーブル内のエントリのオフセットに 1 を加えた値、2 つ目は this の調整値です。最初のワードが奇数なら仮想関数を表します。この ABI はメンバー関数が偶数アドレスから始まることを前提としているためです。
Microsoft の表現はクラスの継承によって変わります。単一継承では、メンバー関数へのポインターは単なる関数のアドレスです。仮想関数の場合、そのアドレスはサンク (thunk) のもので、サンクが仮想呼び出しを行います。多重継承と仮想継承ではフィールドが加わり、まだ定義されていないクラスには最大の形式が使われます。
invoke は RDX にある調整値を this に加え、最初のワードのビット 0 を調べ、セットされていればテーブルから関数を読み込みます。pick はペアを RAX と RDX で返し、size に対しては 1 (エントリ 0 に 1 を加えた値) です。このようなポインターは、どのクラスでも 16 バイトです。Microsoft: invoke はポインターへジャンプするだけで、pick はエントリ 0 を呼び出すサンク ??_9Widget@@$BA@AA を返し、ポインターは 8 バイトです。未定義のクラス 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 構文 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 構文 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 構文 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 構文 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 24例外
Itanium C++ ABI では、throw は __cxa_allocate_exception で例外を確保し、それを型情報とともに __cxa_throw に渡します。__cxa_throw はそれを psABI のアンワインドライブラリに渡し、このライブラリがフレームをたどって各関数のパーソナリティルーチン (personality routine) を呼び出します。C++ のパーソナリティルーチンは __gxx_personality_v0 です。このルーチンは関数の呼び出し箇所と catch 節の表、つまり言語固有データ領域 (language-specific data area、LSDA) を読み、その関数が例外を捕捉するかどうかを判断します。catch ブロックは例外オブジェクトに対して __cxa_begin_catch を呼び出し、処理が終わると __cxa_end_catch を呼び出します。プログラムが try ブロックに入るときには何も実行されません。
.cfi_personality と .cfi_lsda は、safe_parse のアンワインド情報をパーソナリティルーチンとその LSDA に結び付けます。LSDA には .Ltmp0 から .Ltmp1 までの呼び出し箇所が 1 つあり、その着地先は .Ltmp2、アクションは 1 で、型 _ZTIi つまり int を捕捉します。MinGW-w64 向けでは、Clang は同じランタイム関数と表を使いますが、例外は Windows のアンワインドを通って伝わります。__gxx_personality_seh0 が関数の .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 構文 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 構文 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 構文 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 構文 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 の C++ 例外は、Windows の構造化例外処理 (structured exception handling) の上に作られています。throw は、オブジェクトと、その型および捕捉されうる型の記述を渡して _CxxThrowException を呼び出します。x64 では、try を持つ関数のハンドラーである __CxxFrameHandler3 が、関数のコードを状態に、状態を try ブロックとその catch 節に対応付ける表を読みます。各 catch ブロックは、それぞれ独立した小さな関数としてコンパイルされます。32 ビット Windows では、同じハンドラーが関数の入口で FS:0 のチェーンに登録されます。
fail は _TI1H を渡します。これは _CTA1H と _CT??_R0H@84 を経て、int の型記述子 ??_R0H@8 につながります。safe_parse の表は $cppxdata$ から始まり、try ブロック、ファンクレット (funclet) ?catch$2@... が処理する ??_R0H@8 の catch 節、そしてコードアドレスから状態への対応表が続きます。ファンクレットは safe_parse が処理を続けるアドレス .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 構文 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 構文 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 の /EH オプションは、catch (...) が何を捕捉するかを選びます。/EHa では、アクセス違反などのプロセッサーとシステムの構造化例外も捕捉し、/EHsc では C++ の例外だけを捕捉します。
まとめ
| Itanium C++ ABI | Microsoft C++ ABI | |
|---|---|---|
this | 第 1 引数 | 第 1 引数、32 ビット x86 では ECX |
| 名前 | _Z...、テンプレート以外は戻り値の型なし | ?...@Z、戻り値の型と呼び出し規約を含む |
| 仮想関数テーブル | 関数の前に先頭までのオフセットと型情報、キー関数 | 関数の前にロケーター、COMDAT |
| メンバー関数へのポインター | 常に 2 ワード | 継承に応じて 1 から 4 フィールド |
| 例外 | __cxa_throw、パーソナリティルーチンと LSDA | _CxxThrowException、__CxxFrameHandler3 とその表 |
出典
- Itanium C++ ABI: コミット
fae5e46c8614、2026-01-20 - Itanium C++ ABI: Exception Handling: コミット
fae5e46c8614、2026-01-20 - GCC 14.2 manual: Binary Compatibility: 2026-09-25 の公開版
- System V Application Binary Interface, AMD64 Architecture Processor Supplement, version 1.0: コミット
e1ce098331da、2025-03-12 - Microsoft Learn: Decorated names: コミット
f70d88cd5da7、2026-09-24 - Microsoft Learn: __thiscall: コミット
f70d88cd5da7、2026-09-24 - Clang 23.1.2: clang/lib/CodeGen/MicrosoftCXXABI.cpp: コミット
85ac56026243、2026-09-20 - Microsoft Learn: Inheritance keywords: コミット
f70d88cd5da7、2026-09-24 - Microsoft Learn: /vmb, /vmg (Representation method): コミット
f70d88cd5da7、2026-09-24 - Microsoft Learn: /EH (Exception handling model): コミット
f70d88cd5da7、2026-09-24 - Microsoft Learn: x64 exception handling: コミット
f70d88cd5da7、2026-09-24 - LLVM 23.1.2: llvm/lib/Target/X86/X86WinEHState.cpp: コミット
85ac56026243、2026-09-20