home ホーム search 検索 -  login ログイン  | reload edit datainfo version cmd icon diff delete  | help ヘルプ

C言語系/呼び出し規約/x86/thiscall

C言語系/呼び出し規約/x86/thiscall

C言語系 / 呼び出し規約 / x86 / thiscall
id: 621 所有者: msakamoto-sf    作成日: 2010-03-17 10:33:52
カテゴリ: Assembler C言語 UNIX Windows 

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がそのまま適用される。


サンプルコード

foo.cpp (メンバ関数の実装)

#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 (クラス定義ヘッダーファイル)

#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 */

main.cpp (main()関数定義)

#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;
}

VC++2008 Express Edtion

可変長引数を使わない時
  • 引数は右から左へスタックにPUSHされる。
  • "this"ポインタはECX経由で渡される。
  • stdcallと同様、関数(callee)側でスタッククリーンアップする。
可変長引数を使う時
  • 引数は右から左へスタックに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)(抜粋)

(...)
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)(抜粋)

; 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

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)(抜粋)

(...)
_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)(抜粋)

   ;		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

OpenWatcom Compiler(16bit)

可変長引数を使わない時
  • 左から3つまでの引数は、左から順にDX, BX, CXレジスタ経由で渡される。
  • 左から4つ目以降の引数は、右から左へスタックへ積まれる。
  • "this"ポインタはAXレジスタ経由で渡される。
  • stdcallと同様、関数(callee)側でスタッククリーンアップする。

これは丁度 watcall(C言語系/呼び出し規約/x86/watcall)) 呼び出し規約で、"this"ポインタが暗黙的に第一引数に指定された状況と同じになる。watcallでは左から4つめまでの引数が AX, DX, BX, CX の順で渡されるが、最初のAXの分が"this"ポインタに指定され、以降一つ分ずれた形になる。

可変長引数を使う時
  • 引数は右から左へスタックに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)(抜粋)

(...)
`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)(抜粋)

    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 

OpenWatcom Compiler(32bit)

可変長引数を使わない時
  • 左から3つまでの引数は、左から順にEDX, EBX, ECXレジスタ経由で渡される。
  • 左から4つ目以降の引数は、右から左へスタックへ積まれる。
  • "this"ポインタはEAXレジスタ経由で渡される。
  • stdcallと同様、関数(callee)側でスタッククリーンアップする。

これは丁度 watcall(C言語系/呼び出し規約/x86/watcall)) 呼び出し規約で、"this"ポインタが暗黙的に第一引数に指定された状況と同じになる。watcallでは左から4つめまでの引数が EAX, EDX, EBX, ECX の順で渡されるが、最初のEAXの分が"this"ポインタに指定され、以降一つ分ずれた形になる。

可変長引数を使う時
  • 引数は右から左へスタックに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)(抜粋)

(...)
`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)(抜粋)

    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 

Turbo C++ 4.0J

  • 引数は右から左へスタックにPUSHされる。
  • "this"ポインタは最後にスタックにPUSHされる。
  • cdeclと同様、関数を呼ぶ(caller)側でスタッククリーンアップする。

※可変長引数の有無に依らない。
※"using namespace"を削除、"cstdarg" → "stdarg.h" に修正して確認。(これらを行わないと、コンパイルエラーになってしまう)
※TC4Jについてはリンク&実行までは行っていない。

アセンブラコードを生成

> tcc -S foo.cpp
> tcc -S main.cpp

foo.cpp のアセンブラ出力(foo.asm)(抜粋)

(...)
   ;	
   ;	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
(...)

main.cpp のアセンブラ出力(main.asm)(抜粋)

   ;	
   ;		
   ;		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

GCC4.1.2 + binutils-2.7.50.0.6 (CentOS5.2)

  • 引数は右から左へスタックにPUSHされる。
  • "this"ポインタは最後にスタックにPUSHされる。
  • cdeclと同様、関数を呼ぶ(caller)側でスタッククリーンアップする。

※可変長引数の有無に依らない。

特記事項
  • 生成されたアセンブラ上は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)(抜粋)

(...)
.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)(抜粋)

	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


プレーンテキスト形式でダウンロード
現在のバージョン : 7
更新者: msakamoto-sf
更新日: 2010-03-27 13:00:27
md5:5c8585ac906bab64d8906393c0a7a834
sha1:c0bfd943f33bd6d1c4bad19235ebda6c394ea246
コメント
コメントを投稿するにはログインして下さい。