• 函数的调用惯例



    TAG: 调用惯例, cdecl, stdcall, fastcall, thiscall 
    DATE: 2013-08-06

    什么是调用惯例

    调用惯例(Calling Conventions)指计算机程序执行时调用函数或过程的一些约定,包括:

    1. 函数的参数是通过栈还是寄存器传递?
    2. 如果通过栈传递,顺序是怎样的,是从左至右入栈还是相反。
    3. 谁负责清理栈,是调用者还是被调用者?

    从清理栈的角度来讲,调用惯例可分为三类:函数的调用者清理,函数清理,混合清理(有时由调用者清理,有时由函数自己清理)。

    调用者清理

    著名的cdecl就是由函数调用者清理栈的调用惯例。 cdecl是基于c语言的调用惯例,也是x86机器上大多数C编译器采用的调用惯例。

    函数的返回结果多通过EAX寄存器返回。 对于32位机器,EAX能容纳4个字节。 整数或内存地址(指针),通过EAX寄存器返回是没有问题的。 超过4个字节的结构体呢?如何返回?

    通过阅读 http://en.wikipedia.org/wiki/X86_calling_conventions,我找到了答案。 对于较小的结构体或对象,可以通过EAX:EDX寄存器对返回。 对于超大的对象或结构体,caller在调用函数之前会分配出内存空间,然后把这个空间地址作为第一个参数隐式地传给函数。被调用的函数callee把结果写进这片内存空间,再pop空间地址,之后才返回。 对于浮点数的结果,似乎是通过 ST0 x87 register(浮点寄存器)返回的。

    因为调用者知道为参数分配了多少栈空间,所以由调用者清理栈就有一个好处: 为参数分配的栈空间大小可以动态决定。 因此cdecl支持可变参数的函数的调用,例如printf

    如果强迫某个函数使用cdecl调用惯例,可以在函数声明中加_cdecl关键字,如:

    void _cdecl funct();
    

    函数自己清理

    pascalstdcallfastcall都是由函数来清理栈。 通过阅读程序的汇编代码,可以很容易识别这类调用惯例。因为函数返回前会清理栈。

    pascal是基于PASCAL编程语言的函数调用惯例。 参数按照从左到右的顺序压栈(和cdecl的入栈顺序相反)。 OS/2 1.x,Microsoft Windows 3.x 和 Borland Delphi 1.x中的16位API都使用这种函数调用惯例。

    stdcall是从pascal调用惯例演变出来的,和pascal不同的是,stdcall以从右到左的顺序对参数压栈。 返回值存储在EAX寄存器中。Win32 API就是采用的这种调用惯例。

    fastcall是混合使用寄存器和栈来存储函数的参数,比如把前两个参数存储在寄存器中,其余的参数入栈。 有Microsoft fastcallBorland fastcall等不同的实现。

    由函数自己清理栈的好处在于:调用者不需要每次调用函数之后都清理栈,从而节省了不少代码, 从而生成的二进制文件比较小。坏处在于,由于清理栈的代码是事先生成在函数体内, 所以不能支持可变参数的函数。

    混合清理

    混合清理的代表是thiscall,对C++中非静态成员函数使用的就是这种调用惯例。

    对于gcc编译器来说,thiscall几乎和cdecl相同:函数调用者负责清理栈,参数按从右到左的顺序入栈。 不同的是,thiscall最后会把this指针压栈,就好象它是函数的第一个参数。(其实也是的吧)

    对于Microsoft VC++编译器,thiscall类似于Windows API的stdcall,函数的参数从右到左压栈,由参数来清理栈。和stdcall不同的是,thiscall会通过ECX寄存器来传递this指针。因为由函数自己清理栈不支持可变参数的函数调用,所以对于可变参数的函数,则由函数的调用者来清理栈。这是thiscall的灵活之处。

    总结

    调用惯例出栈方参数传递名字修饰
    cdecl 函数调用方 从右至左的顺序压参数入栈 下划线+函数名
    pascal 函数本身 从左至右的顺序入栈 较为复杂,参见pascal文档
    stdcall 函数本身 从右至左的顺序压参数入栈 下划线+函数名+@+参数的字节数, 如函数 int func(int a, double b)的修饰名是 _func@12
    fastcall 函数本身 头两个 DWORD(4字节)类型或者更少字节的参数 被放入寄存器,其他剩下的参数按从右至左的顺序入栈 @+函数名+@+参数的字节数
    thiscall 不一定 从右至左的顺序压参数入栈(有时会通过寄存器传递this指针) 不详
  • 相关阅读:
    996工作制是奋斗还是剥削?
    动态链接的PLT与GOT
    The Product-Minded Software Engineer
    缓冲区溢出
    golang的加法比C快?
    C errno是否是线程安全的
    golang 三个点的用法
    GDB 单步调试汇编
    为什么CPU需要时钟这种概念?
    fliebeat配置手册
  • 原文地址:https://www.cnblogs.com/jiqingwu/p/calling_conventions.html
Copyright © 2020-2023  润新知