MSVC中常见的几种函数调用约定

MSVC中常见的几种函数调用约定,第1张

MSVC中几种常用的函数调用约定 1. calling convention 1.1 cdecl
  1. 参数从右向左压入堆栈
  2. 调用方清理堆栈
  3. 名称修饰:函数名加前缀_
1.2 stdcall
  1. 参数从右向左压入堆栈
  2. 被调用方清理堆栈
  3. 名称修饰:函数名前缀加_,后缀@,之后接参数列表字节数
1.3 fastcall
  1. 前两个小于等于DWORD的参数通过ECX, EDX寄存器传递,其他参数从右向左依次压入堆栈
  2. 被调用方清理堆栈
  3. 名称修饰:函数名前缀加@,后缀@,之后接参数列表字节数
2. prologue and epilogue

prologue 和 epilogue 是函数调用开始时和结束时所作的准备工作,主要和创建和清理堆栈有关,这些代码对于每一个函数都差不多。

2.1 prologue code
push ebp  ; 备份调用方堆栈ebp
mov ebp, esp  ; 为被调用函数堆栈设置ebp
sub esp, n  ; 提升堆栈,用于存放局部变量

值得注意的是,上面最后一条指令 sub esp, n 如果n只有4个字节,通常会被编译器编译为 push ecx ,因为 push 指令长度比sub更小。

2.2 epilogue code
mov esp, ebp  ; 恢复堆栈为提升前的样子
pop ebp  ; 还原调用方ebp
ret

最后一条指令的具体形式和调用约定有关。

3. examples
// cdecl
int __cdecl func_cde(int a)
{
    int local = a;
    return local;
}

// stdcall
int __stdcall func_std(int a, int b)
{
    int local = a;
    return local;
}

// fastcall
int __fastcall func_fast(int a, int b, int c, int d)
{
    int local = a;
    return local;
}


int main()
{
    int local = 1;
    int local1 = 1;

    func_cde(1);
    func_std(1,2);
    func_fast(1,2,3,4);
    return local;
}

以上C语言源代码通过MSVC编译后得到以下汇编指令:

_func_cde PROC
        push    ebp
        mov     ebp, esp
        push    ecx
        mov     eax, DWORD PTR _a$[ebp]
        mov     DWORD PTR _local$[ebp], eax
        mov     eax, DWORD PTR _local$[ebp]
        mov     esp, ebp
        pop     ebp
        ret     0
_func_cde ENDP


_func_std@8 PROC
        push    ebp
        mov     ebp, esp
        push    ecx
        mov     eax, DWORD PTR _a$[ebp]
        mov     DWORD PTR _local$[ebp], eax
        mov     eax, DWORD PTR _local$[ebp]
        mov     esp, ebp
        pop     ebp
        ret     8  ; 被调用方清理堆栈
_func_std@8 ENDP


@func_fast@16 PROC
        push    ebp
        mov     ebp, esp
        sub     esp, 12                       
        mov     DWORD PTR _b$[ebp], edx
        mov     DWORD PTR _a$[ebp], ecx
        mov     eax, DWORD PTR _a$[ebp]
        mov     DWORD PTR _local$[ebp], eax
        mov     eax, DWORD PTR _local$[ebp]
        mov     esp, ebp
        pop     ebp
        ret     8  ; 被调用方清理堆栈
@func_fast@16 ENDP


_main   PROC
        push    ebp
        mov     ebp, esp
        sub     esp, 8
        mov     DWORD PTR _local$[ebp], 1
        mov     DWORD PTR _local1$[ebp], 1
        push    1
        call    _func_cde
        add     esp, 4  ; cedcl需要调用方清理堆栈
        push    2
        push    1
        call    _func_std@8
        push    4
        push    3
        mov     edx, 2  ; fastcall的前两个dword参数通过寄存器ecx和edx传递
        mov     ecx, 1
        call    @func_fast@16
        mov     eax, DWORD PTR _local$[ebp]
        mov     esp, ebp
        pop     ebp
        ret     0
_main   ENDP

以上编译出的汇编代码中,_func_cde_func_std@8@func_fast@16 三个名称反映了MSVC编译器对三种调用约定的函数所采用的名称修饰规则。

调用约定中清理堆栈指的是在函数调用结束后,释放参数所占据堆栈空间,让ESP寄存器回到函数调用前的样子。

不难发现在cedcl中,主调函数main中需要指令 add esp, 4 来恢复一个int类型参数所占据的4个字节堆栈空间,有些编译器上该指令也可能被替换为 pop ecx

stdcall和fastcall中则都是通过被调函数的ret指令完成。

参数传递方面也有区别,fastcall中,前两个int类型的参数是通过寄存器ecx和edx来传递的,而其他参数则通过参数列表从右向左的顺序压入堆栈。

cdecl和stdcall中,参数全部通过堆栈传递。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/674252.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-19
下一篇 2022-04-19

发表评论

登录后才能评论

评论列表(0条)

保存