#navi_header|C言語系| ''thiscall'' 呼び出し規約: - C++でクラスのメンバ関数(インスタンスメソッド)で使われる。 - 引数はスタック上にPUSHする。(主に右から左へPUSH) - ''スタックのクリーンアップは、コンパイラや可変長引数の有無により異なる。'' -- 関数(callee)側でスタックをクリーンアップする場合は、アセンブラレベルでは "RET imm16" 命令を使い、戻る時にスタックポインタを"imm16"バイト分だけ戻す。つまり、"imm16"バイト分だけSPが増える(x86ではスタックはアドレスの小さい方へ進んでいくので、「戻る」=SPのアドレス値は大きくなる)。 -- 関数を呼ぶ(caller)側でスタックをクリーンアップする場合は、アセンブラレベルではCALL命令の直後(=関数からRETした直後)に、スタックポインタ(SP)にPUSHした引数のバイト分だけ即値を加算 (ADD)する。(x86ではスタックはアドレスの小さい方へ進んでいくので、「スタックのクリーンアップ」=「戻る」=SPのアドレス値を大きくする)。 - thiscall呼び出し規約はC++専用のため、C言語での他の呼び出し規約における装飾名のルールは使われず、C++でのmanglingがそのまま適用される。 #more|| ---- #outline|| ---- * サンプルコード ** foo.cpp (メンバ関数の実装) #code|c|> #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; } ||< ** foo.hpp (クラス定義ヘッダーファイル) #code|c|> #include #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 */ ||< ** main.cpp (main()関数定義) #code|c|> #include #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; } ||< * VC++2008 Express Edtion : 可変長引数を使わない時 : #block||> - 引数は右から左へスタックにPUSHされる。 - ''"this"ポインタはECX経由で渡される。'' - stdcallと同様、 ''関数(callee)側で'' スタッククリーンアップする。 ||< : 可変長引数を使う時 : #block||> - 引数は右から左へスタックにPUSHされる。 - ''"this"ポインタは最後にスタックにPUSHされる。'' - cdeclと同様、 ''関数を呼ぶ(caller)側で'' スタッククリーンアップする。 ||< コンパイル&リンク&実行 > 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 ** foo.cpp のアセンブラ出力(foo.asm)(抜粋) #pre||> (...) 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 (...) ||< ** main.cpp のアセンブラ出力(main.asm)(抜粋) #pre||> ; 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 ||< #pre||> ; 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 ||< * Borland C++ Compiler - 引数は右から左へスタックにPUSHされる。 - ''"this"ポインタは最後にスタックにPUSHされる。'' - cdeclと同様、 ''関数を呼ぶ(caller)側で'' スタッククリーンアップする。 ※可変長引数の有無に依らない。 アセンブラコードを生成 > 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 ** foo.cpp のアセンブラ出力(foo.asm)(抜粋) #pre||> (...) _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 (...) ||< ** main.cpp のアセンブラ出力(main.asm)(抜粋) #pre||> ; 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 ||< #pre||> ; 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 ||< * OpenWatcom Compiler(16bit) : 可変長引数を使わない時 : #block||> - 左から3つまでの引数は、左から順にDX, BX, CXレジスタ経由で渡される。 - 左から4つ目以降の引数は、右から左へスタックへ積まれる。 - ''"this"ポインタはAXレジスタ経由で渡される。'' - stdcallと同様、 ''関数(callee)側で'' スタッククリーンアップする。 これは丁度 watcall([[628]])) 呼び出し規約で、"this"ポインタが暗黙的に第一引数に指定された状況と同じになる。watcallでは左から4つめまでの引数が AX, DX, BX, CX の順で渡されるが、最初のAXの分が"this"ポインタに指定され、以降一つ分ずれた形になる。 ||< : 可変長引数を使う時 : #block||> - 引数は右から左へスタックにPUSHされる。 - ''"this"ポインタは最後にスタックにPUSHされる。'' - cdeclと同様、 ''関数を呼ぶ(caller)側で'' スタッククリーンアップする。 ||< 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 ** foo.cpp のアセンブラ出力(foo.asm)(抜粋) #pre||> (...) `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 (...) ||< ** main.cpp のアセンブラ出力(main.asm)(抜粋) #pre||> 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` ||< #pre||> 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 ||< * OpenWatcom Compiler(32bit) : 可変長引数を使わない時 : #block||> - 左から3つまでの引数は、左から順にEDX, EBX, ECXレジスタ経由で渡される。 - 左から4つ目以降の引数は、右から左へスタックへ積まれる。 - ''"this"ポインタはEAXレジスタ経由で渡される。'' - stdcallと同様、 ''関数(callee)側で'' スタッククリーンアップする。 これは丁度 watcall([[628]])) 呼び出し規約で、"this"ポインタが暗黙的に第一引数に指定された状況と同じになる。watcallでは左から4つめまでの引数が EAX, EDX, EBX, ECX の順で渡されるが、最初のEAXの分が"this"ポインタに指定され、以降一つ分ずれた形になる。 ||< : 可変長引数を使う時 : #block||> - 引数は右から左へスタックにPUSHされる。 - ''"this"ポインタは最後にスタックにPUSHされる。'' - cdeclと同様、 ''関数を呼ぶ(caller)側で'' スタッククリーンアップする。 ||< 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 ** foo.cpp のアセンブラ出力(foo.asm)(抜粋) #pre||> (...) `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 (...) ||< ** main.cpp のアセンブラ出力(main.asm)(抜粋) #pre||> 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` ||< #pre||> 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 ||< * Turbo C++ 4.0J ** foo.cpp のアセンブラ出力(foo.asm)(抜粋) ** main.cpp のアセンブラ出力(main.asm)(抜粋) * GCC4.1.2 + binutils-2.7.50.0.6 (CentOS5.2) - 引数は右から左へスタックにPUSHされる。 - ''"this"ポインタは最後にスタックにPUSHされる。'' - cdeclと同様、 ''関数を呼ぶ(caller)側で'' スタッククリーンアップする。 ※可変長引数の有無に依らない。 : 特記事項 : #block||> - ''生成されたアセンブラ上はESPの相対アドレスを使ってMOVしているが、結果としてメモリレイアウト上は右から左へ PUSHされた状態になる。'' - なおgcc側の仕様か、呼び出し側(main())で使用するローカル領域分、冒頭でまとめてスタック上に確保してしまい、以降ESP は動かさずにESPからの相対アドレスを使ってスタック上に値を置いていく。 -- これにより ''関数呼び出し側でのスタッククリーンアップを一切行わずに済む'' ようなアセンブラコードになっている点に注意。 コンパイル&リンク&実行 $ 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 ** foo.cpp のアセンブラ出力(foo.s)(抜粋) #pre||> (...) .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 (...) ||< ** main.cpp のアセンブラ出力(main.s)(抜粋) #pre||> 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 ||< #pre||> 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 ||< #navi_footer|C言語系|