thiscall呼び出し規約:
#include "foo.hpp" // Borland C++ Compilerで必要。VC++2008では有っても無くても影響なし。 // 他のコンパイラは不明。OpenWatcom, TurboC++, GCCでは有りで確認している。 using namespace std; foo::foo(int x) { this->x = x; } int foo::bar(int a, int b, int c, int d, int e) { return a + b + c + d + e + this->x; } int foo::baz(int argn, ...) { va_list ap; int r = this->x; va_start(ap, argn); for (int i = 0; i < argn; i++, va_arg(ap, int)) { r += *(int*)ap; } va_end(ap); return r; }
#include <cstdarg> #ifndef FOO_HPP #define FOO_HPP class foo { protected: int x; public: foo(int x); int bar(int a, int b, int c, int d, int e); int baz(int argn, ...); }; #endif /* FOO_HPP */
#include <iostream> #include "foo.hpp" using namespace std; int main(int argc, char *argv[]) { foo *f; f = new foo(10); int r = f->bar(1, 2, 3, 4, 5); cout << "foo::bar() = " << r << endl; r = f->baz(3, 10, 20, 30); cout << "foo::baz() = " << r << endl; return 0; }
コンパイル&リンク&実行
> cl /c /Od /FAs foo.cpp > cl /c /Od /FAs /EHsc main.cpp > cl main.obj foo.obj > main.exe foo::bar() = 25 foo::baz() = 70
(...) PUBLIC ??0foo@@QAE@H@Z ; foo::foo ; Function compile flags: /Odtp ; File c:\in_vitro\c\cc_thiscall\foo.cpp _TEXT SEGMENT _this$ = -4 ; size = 4 _x$ = 8 ; size = 4 ??0foo@@QAE@H@Z PROC ; foo::foo ; _this$ = ecx ; 3 : foo::foo(int x) { this->x = x; } push ebp mov ebp, esp push ecx mov DWORD PTR _this$[ebp], ecx mov eax, DWORD PTR _this$[ebp] mov ecx, DWORD PTR _x$[ebp] mov DWORD PTR [eax], ecx mov eax, DWORD PTR _this$[ebp] mov esp, ebp pop ebp ret 4 ??0foo@@QAE@H@Z ENDP ; foo::foo _TEXT ENDS PUBLIC ?bar@foo@@QAEHHHHHH@Z ; foo::bar ; Function compile flags: /Odtp _TEXT SEGMENT _this$ = -4 ; size = 4 _a$ = 8 ; size = 4 _b$ = 12 ; size = 4 _c$ = 16 ; size = 4 _d$ = 20 ; size = 4 _e$ = 24 ; size = 4 ?bar@foo@@QAEHHHHHH@Z PROC ; foo::bar ; _this$ = ecx ; 5 : int foo::bar(int a, int b, int c, int d, int e) { push ebp mov ebp, esp push ecx mov DWORD PTR _this$[ebp], ecx ; 6 : return a + b + c + d + e + this->x; mov eax, DWORD PTR _a$[ebp] add eax, DWORD PTR _b$[ebp] add eax, DWORD PTR _c$[ebp] add eax, DWORD PTR _d$[ebp] add eax, DWORD PTR _e$[ebp] mov ecx, DWORD PTR _this$[ebp] add eax, DWORD PTR [ecx] ; 7 : } mov esp, ebp pop ebp ret 20 ; 00000014H ?bar@foo@@QAEHHHHHH@Z ENDP ; foo::bar _TEXT ENDS PUBLIC ?baz@foo@@QAAHHZZ ; foo::baz ; Function compile flags: /Odtp _TEXT SEGMENT _i$2881 = -12 ; size = 4 _ap$ = -8 ; size = 4 _r$ = -4 ; size = 4 _this$ = 8 ; size = 4 _argn$ = 12 ; size = 4 ?baz@foo@@QAAHHZZ PROC ; foo::baz ; 8 : int foo::baz(int argn, ...) { push ebp mov ebp, esp sub esp, 12 ; 0000000cH ; 9 : va_list ap; ; 10 : int r = this->x; mov eax, DWORD PTR _this$[ebp] mov ecx, DWORD PTR [eax] mov DWORD PTR _r$[ebp], ecx ; 11 : va_start(ap, argn); lea edx, DWORD PTR _argn$[ebp+4] mov DWORD PTR _ap$[ebp], edx ; 12 : for (int i = 0; i < argn; i++, va_arg(ap, int)) { mov DWORD PTR _i$2881[ebp], 0 jmp SHORT $LN3@baz $LN2@baz: mov eax, DWORD PTR _i$2881[ebp] add eax, 1 mov DWORD PTR _i$2881[ebp], eax mov ecx, DWORD PTR _ap$[ebp] add ecx, 4 mov DWORD PTR _ap$[ebp], ecx $LN3@baz: mov edx, DWORD PTR _i$2881[ebp] cmp edx, DWORD PTR _argn$[ebp] jge SHORT $LN1@baz ; 13 : r += *(int*)ap; mov eax, DWORD PTR _ap$[ebp] mov ecx, DWORD PTR _r$[ebp] add ecx, DWORD PTR [eax] mov DWORD PTR _r$[ebp], ecx ; 14 : } jmp SHORT $LN2@baz $LN1@baz: ; 15 : va_end(ap); mov DWORD PTR _ap$[ebp], 0 ; 16 : return r; mov eax, DWORD PTR _r$[ebp] ; 17 : } mov esp, ebp pop ebp ret 0 ?baz@foo@@QAAHHZZ ENDP ; foo::baz _TEXT ENDS (...)
; 11 : int r = f->bar(1, 2, 3, 4, 5); push 5 push 4 push 3 push 2 push 1 mov ecx, DWORD PTR _f$[ebp] call ?bar@foo@@QAEHHHHHH@Z ; foo::bar mov DWORD PTR _r$[ebp], eax
; 15 : r = f->baz(3, 10, 20, 30); push 30 ; 0000001eH push 20 ; 00000014H push 10 ; 0000000aH push 3 mov eax, DWORD PTR _f$[ebp] push eax call ?baz@foo@@QAAHHZZ ; foo::baz add esp, 20 ; 00000014H mov DWORD PTR _r$[ebp], eax
※可変長引数の有無に依らない。
アセンブラコードを生成
> bcc32 -Od -S foo.cpp > bcc32 -Od -S main.cpp
コンパイル&リンク&実行
> bcc32 -Od -c foo.cpp > bcc32 -Od -c main.cpp > bcc32 main.obj foo.obj > main.exe foo::bar() = 25 foo::baz() = 70
(...) _TEXT segment dword public use32 'CODE' @foo@$bctr$qi segment virtual @@foo@$bctr$qi proc near ?live16385@0: ; ; foo::foo(int x) { this->x = x; } ; push ebp mov ebp,esp add esp,-36 @1: mov eax,offset @@_$ECTA$@foo@$bctr$qi call @__InitExceptBlockLDTC mov edx,dword ptr [ebp+12] mov ecx,dword ptr [ebp+8] mov dword ptr [ecx],edx mov eax,dword ptr [ebp-36] mov dword ptr fs:[0],eax mov eax,dword ptr [ebp+8] @3: @2: mov esp,ebp pop ebp ret @@foo@$bctr$qi endp @foo@$bctr$qi ends _TEXT ends _TEXT segment dword public use32 'CODE' @foo@bar$qiiiii segment virtual @@foo@bar$qiiiii proc near ?live16387@0: ; ; int foo::bar(int a, int b, int c, int d, int e) { ; push ebp mov ebp,esp ; ; return a + b + c + d + e + this->x; ; @4: mov eax,dword ptr [ebp+12] add eax,dword ptr [ebp+16] add eax,dword ptr [ebp+20] add eax,dword ptr [ebp+24] add eax,dword ptr [ebp+28] mov edx,dword ptr [ebp+8] add eax,dword ptr [edx] ; ; } ; @6: @5: pop ebp ret @@foo@bar$qiiiii endp @foo@bar$qiiiii ends _TEXT ends _TEXT segment dword public use32 'CODE' @foo@baz$qie segment virtual @@foo@baz$qie proc near ?live16388@0: ; ; int foo::baz(int argn, ...) { ; push ebp mov ebp,esp add esp,-12 ; ; va_list ap; ; int r = this->x; ; @7: mov eax,dword ptr [ebp+8] mov edx,dword ptr [eax] mov dword ptr [ebp-8],edx ; ; va_start(ap, argn); ; lea ecx,dword ptr [ebp+16] mov dword ptr [ebp-4],ecx ; ; for (int i = 0; i < argn; i++, va_arg(ap, int)) { ; @8: xor eax,eax mov dword ptr [ebp-12],eax mov edx,dword ptr [ebp-12] cmp edx,dword ptr [ebp+12] jge short @10 ; ; r += *(int*)ap; ; @9: mov ecx,dword ptr [ebp-4] mov eax,dword ptr [ecx] add dword ptr [ebp-8],eax @11: inc dword ptr [ebp-12] add dword ptr [ebp-4],4 mov edx,dword ptr [ebp-12] cmp edx,dword ptr [ebp+12] jl short @9 ; ; } ; va_end(ap); ; return r; ; @10: mov eax,dword ptr [ebp-8] ; ; } ; @14: @13: mov esp,ebp pop ebp ret @@foo@baz$qie endp @foo@baz$qie ends _TEXT ends (...)
; int r = f->bar(1, 2, 3, 4, 5); ; push 5 push 4 push 3 push 2 push 1 push dword ptr [ebp-44] call @@foo@bar$qiiiii add esp,24 mov dword ptr [ebp-48],eax
; r = f->baz(3, 10, 20, 30); ; push 30 push 20 push 10 push 3 push dword ptr [ebp-44] call @@foo@baz$qie add esp,20 mov dword ptr [ebp-48],eax
これは丁度 watcall(C言語系/呼び出し規約/x86/watcall)) 呼び出し規約で、"this"ポインタが暗黙的に第一引数に指定された状況と同じになる。watcallでは左から4つめまでの引数が AX, DX, BX, CX の順で渡されるが、最初のAXの分が"this"ポインタに指定され、以降一つ分ずれた形になる。
OpenWatcom Compiler(16bit)では、foo.cppのfoo::baz()メソッドで以下の修正を行う必要があった。
b = *(int*)ap;
→
b = **ap;
コンパイル&リンク&実行
> wpp -od -d0 foo.cpp > wpp -od -d0 main.cpp > wcl -fe=main16.exe main.obj foo.obj > main16.exe foo::bar() = 25 foo::baz() = 70
アセンブラ生成
> wdis -a -l=foo.asm foo.obj > wdis -a -l=main.asm main.obj
(...) `W?$ct:foo$n(i)_`: push ax mov ax,12H call near ptr __STK pop ax push bx push cx push si push di push bp mov bp,sp sub sp,6 mov word ptr -6[bp],ax mov word ptr -4[bp],dx mov ax,word ptr -4[bp] mov bx,word ptr -6[bp] mov word ptr [bx],ax mov bx,word ptr -6[bp] mov word ptr -2[bp],bx mov ax,word ptr -2[bp] mov sp,bp pop bp pop di pop si pop cx pop bx ret `W?bar$:foo$n(iiiii)i`: push ax mov ax,12H call near ptr __STK pop ax push si push di push bp mov bp,sp sub sp,0aH mov word ptr -0aH[bp],ax mov word ptr -8[bp],dx mov word ptr -6[bp],bx mov word ptr -4[bp],cx mov bx,word ptr -8[bp] add bx,word ptr -6[bp] add bx,word ptr -4[bp] add bx,word ptr 8[bp] mov ax,word ptr 0aH[bp] add ax,bx mov bx,word ptr -0aH[bp] mov bx,word ptr [bx] add bx,ax mov word ptr -2[bp],bx mov ax,word ptr -2[bp] mov sp,bp pop bp pop di pop si ret 4 `W?baz$:foo$n(ie)i`: mov ax,16H call near ptr __STK push bx push cx push dx push si push di push bp mov bp,sp sub sp,8 mov bx,word ptr 0eH[bp] mov ax,word ptr [bx] mov word ptr -8[bp],ax mov word ptr -4[bp],0 lea ax,12H[bp] mov word ptr -6[bp],ax mov word ptr -2[bp],0 jmp L$2 L$1: inc word ptr -2[bp] add word ptr -6[bp],2 L$2: mov ax,word ptr -2[bp] cmp ax,word ptr 10H[bp] jge L$3 mov bx,word ptr -6[bp] mov al,byte ptr [bx] xor ah,ah mov word ptr -4[bp],ax mov ax,word ptr -4[bp] add word ptr -8[bp],ax jmp L$1 L$3: mov word ptr -6[bp],0 mov ax,word ptr -8[bp] mov sp,bp pop bp pop di pop si pop dx pop cx pop bx ret (...)
mov ax,5 push ax mov ax,4 push ax mov cx,3 mov bx,2 mov dx,1 mov ax,word ptr -22H[bp] call near ptr `W?bar$:foo$n(iiiii)i`
mov ax,1eH push ax mov ax,14H push ax mov ax,0aH push ax mov ax,3 push ax push word ptr -22H[bp] call near ptr `W?baz$:foo$n(ie)i` add sp,0aH
これは丁度 watcall(C言語系/呼び出し規約/x86/watcall)) 呼び出し規約で、"this"ポインタが暗黙的に第一引数に指定された状況と同じになる。watcallでは左から4つめまでの引数が EAX, EDX, EBX, ECX の順で渡されるが、最初のEAXの分が"this"ポインタに指定され、以降一つ分ずれた形になる。
OpenWatcom Compiler(32bit)では、foo.cppのfoo::baz()メソッドで以下の修正を行う必要があった。
b = *(int*)ap;
→
b = **ap;
コンパイル&リンク&実行
> wpp386 -od -d0 foo.cpp > wpp386 -od -d0 main.cpp > wcl386 -fe=main32.exe main.obj foo.obj > main32.exe foo::bar() = 25 foo::baz() = 70
アセンブラ生成
> wdis -a -l=foo.asm foo.obj > wdis -a -l=main.asm main.obj
(...) `W?bar$:foo$n(iiiii)i`: push 24H call near ptr FLAT:__CHK push esi push edi push ebp mov ebp,esp sub esp,14H mov dword ptr -14H[ebp],eax mov dword ptr -10H[ebp],edx mov dword ptr -0cH[ebp],ebx mov dword ptr -8[ebp],ecx mov eax,dword ptr -10H[ebp] add eax,dword ptr -0cH[ebp] add eax,dword ptr -8[ebp] add eax,dword ptr 10H[ebp] mov edx,dword ptr 14H[ebp] add edx,eax mov eax,dword ptr -14H[ebp] mov eax,dword ptr [eax] add eax,edx mov dword ptr -4[ebp],eax mov eax,dword ptr -4[ebp] mov esp,ebp pop ebp pop edi pop esi ret 8 `W?baz$:foo$n(ie)i`: push 2cH call near ptr FLAT:__CHK push ebx push ecx push edx push esi push edi push ebp mov ebp,esp sub esp,10H mov eax,dword ptr 1cH[ebp] mov eax,dword ptr [eax] mov dword ptr -10H[ebp],eax mov dword ptr -8[ebp],0 lea eax,24H[ebp] mov dword ptr -0cH[ebp],eax mov dword ptr -4[ebp],0 jmp L$2 L$1: inc dword ptr -4[ebp] add dword ptr -0cH[ebp],4 L$2: mov eax,dword ptr -4[ebp] cmp eax,dword ptr 20H[ebp] jge L$3 mov eax,dword ptr -0cH[ebp] movzx eax,byte ptr [eax] mov dword ptr -8[ebp],eax mov eax,dword ptr -8[ebp] add dword ptr -10H[ebp],eax jmp L$1 L$3: mov dword ptr -0cH[ebp],0 mov eax,dword ptr -10H[ebp] mov esp,ebp pop ebp pop edi pop esi pop edx pop ecx pop ebx ret (...)
push 5 push 4 mov ecx,3 mov ebx,2 mov edx,1 mov eax,dword ptr -40H[ebp] call near ptr FLAT:`W?bar$:foo$n(iiiii)i`
push 1eH push 14H push 0aH push 3 push dword ptr -40H[ebp] call near ptr FLAT:`W?baz$:foo$n(ie)i` add esp,14H
※可変長引数の有無に依らない。
※"using namespace"を削除、"cstdarg" → "stdarg.h" に修正して確認。(これらを行わないと、コンパイルエラーになってしまう)
※TC4Jについてはリンク&実行までは行っていない。
アセンブラコードを生成
> tcc -S foo.cpp > tcc -S main.cpp
(...) ; ; int foo::bar(int a, int b, int c, int d, int e) { ; assume cs:_TEXT,ds:DGROUP @foo@bar$qiiiii proc near push bp mov bp,sp push si mov si,word ptr [bp+4] ; ; return a + b + c + d + e + this->x; ; mov ax,word ptr [bp+6] add ax,word ptr [bp+8] add ax,word ptr [bp+10] add ax,word ptr [bp+12] add ax,word ptr [bp+14] add ax,word ptr [si] jmp short @2@58 @2@58: ; ; } ; pop si pop bp ret @foo@bar$qiiiii endp ; ; int foo::baz(int argn, ...) { ; assume cs:_TEXT,ds:DGROUP @foo@baz$qie proc near enter 2,0 push si push di mov si,word ptr [bp+4] ; ; va_list ap; ; int r = this->x; ; mov dx,word ptr [si] ; ; int b = 0; ; xor di,di ; ; va_start(ap, argn); ; lea ax,word ptr [bp+8] mov word ptr [bp-2],ax ; ; for (int i = 0; i < argn; i++, va_arg(ap, int)) { ; xor cx,cx jmp short @3@114 @3@58: ; ; b = *(int*)ap; ; mov bx,word ptr [bp-2] mov di,word ptr [bx] ; ; r += b; ; add dx,di inc cx add word ptr [bp-2],2 mov bx,word ptr [bp-2] @3@114: cmp cx,word ptr [bp+6] jl short @3@58 ; ; } ; va_end(ap); ; ; ; return r; ; mov ax,dx jmp short @3@170 @3@170: ; ; } ; pop di pop si leave ret @foo@baz$qie endp (...)
; ; ; int r = f->bar(1, 2, 3, 4, 5); ; push 5 push 4 push 3 push 2 push 1 ; この上で、thisポインタがDIレジスタに格納済 push di call near ptr @foo@bar$qiiiii add sp,12
; ; ; r = f->baz(3, 10, 20, 30); ; push 30 push 20 push 10 push 3 ; この上で、thisポインタがDIレジスタに格納済 push di call near ptr @foo@baz$qie add sp,10
※可変長引数の有無に依らない。
コンパイル&リンク&実行
$ g++ -O0 -Wall --save-temps -c foo.cpp $ g++ -O0 -Wall --save-temps -c main.cpp $ g++ -o main main.o foo.o $ ./main foo::bar() = 25 foo::baz() = 70
(...) .globl _ZN3foo3barEiiiii .type _ZN3foo3barEiiiii, @function _ZN3foo3barEiiiii: .LFB5: pushl %ebp .LCFI4: movl %esp, %ebp .LCFI5: movl 16(%ebp), %eax addl 12(%ebp), %eax addl 20(%ebp), %eax addl 24(%ebp), %eax movl %eax, %edx addl 28(%ebp), %edx movl 8(%ebp), %eax movl (%eax), %eax leal (%edx,%eax), %eax popl %ebp ret .LFE5: .size _ZN3foo3barEiiiii, .-_ZN3foo3barEiiiii .align 2 .globl _ZN3foo3bazEiz .type _ZN3foo3bazEiz, @function _ZN3foo3bazEiz: .LFB6: pushl %ebp .LCFI6: movl %esp, %ebp .LCFI7: subl $16, %esp .LCFI8: movl 8(%ebp), %eax movl (%eax), %eax movl %eax, -12(%ebp) movl $0, -8(%ebp) leal 16(%ebp), %eax movl %eax, -16(%ebp) movl $0, -4(%ebp) jmp .L8 .L9: movl -16(%ebp), %eax movl (%eax), %eax movl %eax, -8(%ebp) movl -8(%ebp), %eax addl %eax, -12(%ebp) addl $1, -4(%ebp) movl -16(%ebp), %eax addl $4, %eax movl %eax, -16(%ebp) .L8: movl -4(%ebp), %eax cmpl 12(%ebp), %eax jl .L9 movl -12(%ebp), %eax leave ret .LFE6: .size _ZN3foo3bazEiz, .-_ZN3foo3bazEiz (...)
movl $5, 20(%esp) movl $4, 16(%esp) movl $3, 12(%esp) movl $2, 8(%esp) movl $1, 4(%esp) movl -16(%ebp), %eax movl %eax, (%esp) .LEHB2: call _ZN3foo3barEiiiii
movl $30, 16(%esp) movl $20, 12(%esp) movl $10, 8(%esp) movl $3, 4(%esp) movl -16(%ebp), %eax movl %eax, (%esp) call _ZN3foo3bazEiz