#navi_header|C言語系| ''__cdecl'' 呼び出し規約: - 引数はスタック上にPUSHする。(主に右から左へPUSH) - ''関数を呼ぶ(caller)側でスタックをクリーンアップする。'' -- アセンブラレベルではCALL命令の直後(=関数からRETした直後)に、スタックポインタ(SP)にPUSHした引数のバイト分だけ即値を加算(ADD)する。(x86ではスタックはアドレスの小さい方へ進んでいくので、「スタックのクリーンアップ」=「戻る」=SPのアドレス値を大きくする)。 - ''可変長引数を使う場合はこの呼び出し規約を使う必要がある。'' -- 通常、引数がいくつPUSHされたのかを正確に判断出来るのは関数を呼ぶ側(caller)だけ。よってスタックのクリーンアップも関数を呼ぶ側(caller)で行う "__cdecl" を使う必要がある。 -- 装飾名に引数のスタック上のバイト数が含まれないのも、可変長引数を意識しているため。 - UNIX系のシステムの場合は、基本的に "__cdecl" が使われる。 example: PUSH arg3 ; 4byte PUSH arg2 ; 2byte PUSH arg1 ; 1byte CALL _foobar ADD ESP, 7 ; スタックポインタを7バイト戻す #more|| ---- #outline|| ---- * サンプルコード ** callee.c (呼ばれる関数側) #code|c|> int foo1(int a) { return a * 2; } int foo2(int a, int b) { return a + b; } int foo3(int a, int b, int c) { return a + b + c; } int foo4(int a, int b, int c, int d) { return a + b + c + d; } ||< ** caller.c (呼ぶ main() 側) #code|c|> #include extern int foo1(int a); extern int foo2(int a, int b); extern int foo3(int a, int b, int c); extern int foo4(int a, int b, int c, int d); int __cdecl main(int argc, char *argv[]) { printf("foo1() = %d\n", foo1(10)); printf("foo2() = %d\n", foo2(10, 20)); printf("foo3() = %d\n", foo3(10, 20, 30)); printf("foo4() = %d\n", foo4(10, 20, 30, 40)); return 0; } ||< * VC++2008 Express Edtion : 指定方法 : #block||> int __cdecl foobar(); typedef int (__cdecl *ptr)(); ||< : コンパイラオプション : "/Gd", ''C言語で他の呼び出し規約が指定されていない場合のデフォルト'' : 装飾名 : "_" + 関数名 コンパイル&リンク&実行 #pre||> > cl /FAs /TC /Od /nologo /c /Gd /Focallee_cdecl.obj callee.c > cl /FAs /TC /Od /nologo /c /Gd /Focaller_cdecl.obj caller.c > link /SUBSYSTEM:CONSOLE /NOLOGO /OUT:cdecl.exe caller_cdecl.obj callee_cdecl.obj > cdecl.exe foo1() = 20 foo2() = 30 foo3() = 60 foo4() = 100 ||< ** callee.c のアセンブラ出力(callee.asm) #pre||> ; Listing generated by Microsoft (R) Optimizing Compiler Version 15.00.30729.01 TITLE C:\in_vitro\c\calling_conventions\callee.c .686P .XMM include listing.inc .model flat INCLUDELIB LIBCMT INCLUDELIB OLDNAMES PUBLIC _foo1 ; Function compile flags: /Odtp ; File c:\in_vitro\c\calling_conventions\callee.c _TEXT SEGMENT _a$ = 8 ; size = 4 _foo1 PROC ; 1 : int foo1(int a) { return a * 2; } push ebp mov ebp, esp mov eax, DWORD PTR _a$[ebp] shl eax, 1 pop ebp ret 0 _foo1 ENDP _TEXT ENDS PUBLIC _foo2 ; Function compile flags: /Odtp _TEXT SEGMENT _a$ = 8 ; size = 4 _b$ = 12 ; size = 4 _foo2 PROC ; 2 : int foo2(int a, int b) { return a + b; } push ebp mov ebp, esp mov eax, DWORD PTR _a$[ebp] add eax, DWORD PTR _b$[ebp] pop ebp ret 0 _foo2 ENDP _TEXT ENDS PUBLIC _foo3 ; Function compile flags: /Odtp _TEXT SEGMENT _a$ = 8 ; size = 4 _b$ = 12 ; size = 4 _c$ = 16 ; size = 4 _foo3 PROC ; 3 : int foo3(int a, int b, int c) { return a + b + c; } push ebp mov ebp, esp mov eax, DWORD PTR _a$[ebp] add eax, DWORD PTR _b$[ebp] add eax, DWORD PTR _c$[ebp] pop ebp ret 0 _foo3 ENDP _TEXT ENDS PUBLIC _foo4 ; Function compile flags: /Odtp _TEXT SEGMENT _a$ = 8 ; size = 4 _b$ = 12 ; size = 4 _c$ = 16 ; size = 4 _d$ = 20 ; size = 4 _foo4 PROC ; 4 : int foo4(int a, int b, int c, int d) { return a + b + c + d; } push ebp mov ebp, esp mov eax, DWORD PTR _a$[ebp] add eax, DWORD PTR _b$[ebp] add eax, DWORD PTR _c$[ebp] add eax, DWORD PTR _d$[ebp] pop ebp ret 0 _foo4 ENDP _TEXT ENDS END ||< ** caller.cのアセンブラ出力(caller.asm) #pre||> ; Listing generated by Microsoft (R) Optimizing Compiler Version 15.00.30729.01 TITLE C:\in_vitro\c\calling_conventions\caller.c .686P .XMM include listing.inc .model flat INCLUDELIB LIBCMT INCLUDELIB OLDNAMES _DATA SEGMENT $SG2496 DB 'foo1() = %d', 0aH, 00H ORG $+3 $SG2497 DB 'foo2() = %d', 0aH, 00H ORG $+3 $SG2498 DB 'foo3() = %d', 0aH, 00H ORG $+3 $SG2499 DB 'foo4() = %d', 0aH, 00H _DATA ENDS PUBLIC _main EXTRN _foo4:PROC EXTRN _foo3:PROC EXTRN _foo2:PROC EXTRN _printf:PROC EXTRN _foo1:PROC ; Function compile flags: /Odtp ; File c:\in_vitro\c\calling_conventions\caller.c _TEXT SEGMENT _argc$ = 8 ; size = 4 _argv$ = 12 ; size = 4 _main PROC ; 9 : { push ebp mov ebp, esp ; 10 : printf("foo1() = %d\n", foo1(10)); push 10 ; 0000000aH call _foo1 add esp, 4 push eax push OFFSET $SG2496 call _printf add esp, 8 ; 11 : printf("foo2() = %d\n", foo2(10, 20)); push 20 ; 00000014H push 10 ; 0000000aH call _foo2 add esp, 8 push eax push OFFSET $SG2497 call _printf add esp, 8 ; 12 : printf("foo3() = %d\n", foo3(10, 20, 30)); push 30 ; 0000001eH push 20 ; 00000014H push 10 ; 0000000aH call _foo3 add esp, 12 ; 0000000cH push eax push OFFSET $SG2498 call _printf add esp, 8 ; 13 : printf("foo4() = %d\n", foo4(10, 20, 30, 40)); push 40 ; 00000028H push 30 ; 0000001eH push 20 ; 00000014H push 10 ; 0000000aH call _foo4 add esp, 16 ; 00000010H push eax push OFFSET $SG2499 call _printf add esp, 8 ; 14 : return 0; xor eax, eax ; 15 : } pop ebp ret 0 _main ENDP _TEXT ENDS END ||< * Borland C++ Compiler : 指定方法 : #block||> int __cdecl foobar(); typedef int (__cdecl *ptr)(); ||< : コンパイラオプション : "-p-" or "-pc", ''C言語で他の呼び出し規約が指定されていない場合のデフォルト'' : 装飾名 : "_" + 関数名 アセンブラコードを生成 > bcc32 -Od -pc -S callee.c > bcc32 -Od -pc -S caller.c コンパイル&リンク&実行 #pre||> > bcc32 -Od -pc -c -ocallee_cdecl.obj callee.c > bcc32 -Od -pc -c -ocaller_cdecl.obj caller.c > ilink32 /c /ap /Tpe c0x32 caller_cdecl callee_cdecl, cdecl, , cw32 import32 > cdecl.exe foo1() = 20 foo2() = 30 foo3() = 60 foo4() = 100 ||< ** callee.c のアセンブラ出力(callee.asm) #pre||> .386p ifdef ??version if ??version GT 500H .mmx endif endif model flat ifndef ??version ?debug macro endm endif ?debug S "callee.c" ?debug T "callee.c" _TEXT segment dword public use32 'CODE' _TEXT ends _DATA segment dword public use32 'DATA' _DATA ends _BSS segment dword public use32 'BSS' _BSS ends DGROUP group _BSS,_DATA _TEXT segment dword public use32 'CODE' _foo1 proc near ?live1@0: ; ; int foo1(int a) { return a * 2; } ; push ebp mov ebp,esp @1: mov eax,dword ptr [ebp+8] add eax,eax @3: @2: pop ebp ret _foo1 endp _foo2 proc near ?live1@32: ; ; int foo2(int a, int b) { return a + b; } ; push ebp mov ebp,esp @4: mov eax,dword ptr [ebp+8] add eax,dword ptr [ebp+12] @6: @5: pop ebp ret _foo2 endp _foo3 proc near ?live1@64: ; ; int foo3(int a, int b, int c) { return a + b + c; } ; push ebp mov ebp,esp @7: mov eax,dword ptr [ebp+8] add eax,dword ptr [ebp+12] add eax,dword ptr [ebp+16] @9: @8: pop ebp ret _foo3 endp _foo4 proc near ?live1@96: ; ; int foo4(int a, int b, int c, int d) { return a + b + c + d; } ; push ebp mov ebp,esp @10: mov eax,dword ptr [ebp+8] add eax,dword ptr [ebp+12] add eax,dword ptr [ebp+16] add eax,dword ptr [ebp+20] @12: @11: pop ebp ret _foo4 endp _TEXT ends public _foo1 public _foo2 public _foo3 public _foo4 ?debug D "callee.c" 15473 24064 end ||< ** caller.cのアセンブラ出力(caller.asm) ※引数が一つの"foo1()"の呼び出しのみ、スタックのクリーンアップが add esp,4 ではなく、 pop ecx となっている。結果としては4バイトスタックポインタが巻戻り、ECXレジスタも他で特に使っていない為副作用も無い。最適化がONになっているためかと思い、"-Od"(最適化を無効)にしても変わらなかった。 #pre||> .386p ifdef ??version if ??version GT 500H .mmx endif endif model flat ifndef ??version ?debug macro endm endif ?debug S "caller.c" ?debug T "caller.c" _TEXT segment dword public use32 'CODE' _TEXT ends _DATA segment dword public use32 'DATA' _DATA ends _BSS segment dword public use32 'BSS' _BSS ends DGROUP group _BSS,_DATA _TEXT segment dword public use32 'CODE' _main proc near ?live1@0: ; ; int __cdecl main(int argc, char *argv[]) ; push ebp mov ebp,esp ; ; { ; printf("foo1() = %d\n", foo1(10)); ; @1: push 10 call _foo1 pop ecx push eax push offset s@ call _printf add esp,8 ; ; printf("foo2() = %d\n", foo2(10, 20)); ; push 20 push 10 call _foo2 add esp,8 push eax push offset s@+13 call _printf add esp,8 ; ; printf("foo3() = %d\n", foo3(10, 20, 30)); ; push 30 push 20 push 10 call _foo3 add esp,12 push eax push offset s@+26 call _printf add esp,8 ; ; printf("foo4() = %d\n", foo4(10, 20, 30, 40)); ; push 40 push 30 push 20 push 10 call _foo4 add esp,16 push eax push offset s@+39 call _printf add esp,8 ; ; return 0; ; xor eax,eax ; ; } ; @3: @2: pop ebp ret _main endp _TEXT ends _DATA segment dword public use32 'DATA' s@ label byte ; s@+0: db "foo1() = %d",10,0 ; s@+13: db "foo2() = %d",10,0 ; s@+26: db "foo3() = %d",10,0 ; s@+39: db "foo4() = %d",10,0 align 4 _DATA ends _TEXT segment dword public use32 'CODE' _TEXT ends public _main extrn __setargv__:near extrn _printf:near extrn _foo1:near extrn _foo2:near extrn _foo3:near extrn _foo4:near ?debug D "C:\in_vitro\apps\borland\bcc55\include\_nfile.h" 10339 10240 ?debug D "C:\in_vitro\apps\borland\bcc55\include\_null.h" 10339 10240 ?debug D "C:\in_vitro\apps\borland\bcc55\include\_defs.h" 10339 10240 ?debug D "C:\in_vitro\apps\borland\bcc55\include\_stddef.h" 10339 10240 ?debug D "C:\in_vitro\apps\borland\bcc55\include\stdio.h" 10339 10240 ?debug D "caller.c" 15473 24232 end ||< * MinGW/MSYS * OpenWatcom Compiler(16bit) * OpenWatcom Compiler(32bit) * Turbo C++ 4.0J * GCC4.1.2 + binutils-2.7.50.0.6 (CentOS5.2) #navi_footer|C言語系|