美文网首页
函数调用约束

函数调用约束

作者: 突然的自我_39c1 | 来源:发表于2021-09-17 11:13 被阅读0次

什么是调用约定

函数的调用约定,顾名思义就是对函数调用的一个约束和规定(规范),描述了函数参数是怎么传递和由谁清除堆栈的。

  • 函数参数的压栈顺序
  • 由调用者还是被调用者把参数弹出栈
  • 以及产生函数修饰名的方法

函数组成:返回值类型 调用约定 函数名(参数列表...)

void __cdecl function(int a,int b);
void __stdcall function(int a,int b);

上面的__cdecl和__stdcall就是调用约定,其中__cdecl是C和C++默认的调用约定;译器默认会为我们使用__cdecl调用约定;常见的调用约定有__cdecl、__stdcall、fastcall,应用最广泛的是__cdecl和__stdcall,下面我们会详细进行讲述。。还有一些不常见的,如 __pascal、__thiscall、__vectorcall。

声明和定义处调用约定必须要相同

在VC++中,调用约定是函数类型的一部分,因此函数的声明和定义处调用约定要相同,不能只在声明处有调用约定,而定义处没有或与声明不同。

int __cdecl function(int a,int b);
int __cdecl function(int a,int b)
{
      return a+b;
}

函数的调用过程

要深入理解函数调用约定,你须要了解函数的调用过程和调用细节
假设函数A调用函数B,我们称A函数为”调用者”,B函数为“被调用者”。如下面的代码

int B(int a,int b)
{
    return a+b;
}
void A()
{
    std::cout()<<B(5,6)<<std::endl;
}

函数调用过程可以这么描述:

  • 先将调用者(A)的堆栈的基址(ebp)入栈,以保存之前任务的信息。
  • 然后将调用者(A)的栈顶指针(esp)的值赋给ebp,作为新的基址(即被调用者B的栈底)。
  • 然后在这个基址(被调用者B的栈底)上开辟(一般用sub指令)相应的空间用作被调用者B的栈空间。
  • 函数B返回后,从当前栈帧的ebp即恢复为调用者A的栈顶(esp),使栈顶恢复函数B被调用前的位置;然后调用者A再从恢复后的栈顶可弹出之前的ebp值(可以这么做是因为这个值在函数调用前一步被压入堆栈)。这样,ebp和esp就都恢复了调用函数B前的位置,也就是栈恢复函数B调用前的状态。
    这个过程在AT&T汇编中通过两条指令完成
move %ebp ,%esp
pop  %ebp

__cdecl的特点

__cdecl 是 C Declaration 的缩写,表示 C 和 C++ 默认的函数调用约定。是C/C++和MFCX的默认调用约定;

  • 按从右至左的顺序压参数入栈、。
  • 由调用者把参数弹出栈。切记:对于传送参数的内存栈是由调用者来维护的,返回值在EAX中。因此对于像printf这样可变参数的函数必须用这种约定。
  • 编译器在编译的时候对这种调用规则的函数生成修饰名的时候,在输出函数名前加上一个下划线前缀,格式为_function。如函数int add(int a, int b)的修饰名是_add。

从代码和程序调试的层面考虑,参数的压栈顺序和栈的清理我们都不用太观注,因为这是编译器的决定的,我们改变不了。但第三点却常常困扰我们,因为如果不弄清楚这点,在多个库之间(如dll、lib、exe)相互调用、依赖时常常出出现莫名其妙的错误。这个我在后面章节会进行详细介绍。

__stdcall的特点

__stdcall是Standard Call的缩写,是C++的标准调用方式,当然这是微软定义的标准,__stdcall通常用于Win32 API中(可查看WINAPI的定义)。

  • 按从右至左的顺序压参数入栈。
  • 由被调用者把参数弹出栈。切记:函数自己在退出时清空堆栈,返回值在EAX中。
  • __stdcall调用约定在输出函数名前加上一个下划线前缀,后面加上一个“@”符号和其参数的字节数,格式为_function@number。如函数int sub(int a, int b)的修饰名是_sub@8。

__fastcall的特点

__fastcall调用的主要特点就是快,因为它是通过寄存器来传送参数的。

  • 实际上__fastcall用ECX和EDX传送前两个DWORD或更小的参数,剩下的参数仍自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈。
  • __fastcall调用约定在输出函数名前加上一个“@”符号,后面也是一个“@”符号和其参数的字节数,格式为@function@number,如double multi(double a, double b)的修饰名是@multi@16。
  • __fastcall和__stdcall很象,唯一差别就是头两个参数通过寄存器传送。注意通过寄存器传送的两个参数是从左向右的,即第1个参数进ECX,第2个进EDX,其他参数是从右向左的入栈,返回仍然通过EAX。

__thiscall的特点

__thiscall是C++类成员函数缺省的调用约定,但它没有显示的声明形式。。因为在C++类中,成员函数调用还有一个this指针参数,因此必须特殊处理,thiscall调用约定的特点:

  • 参数入栈:参数从右向左入栈
  • this指针入栈:如果参数个数确定,this指针通过ecx传递给被调用者;如果参数个数不确定,this指针在所有参数压栈后被压入栈。
  • 栈恢复:对参数个数不定的,调用者清理栈,否则函数自己清理栈。

相关文章

  • typescript学习笔记4-接口(2)

    描述函数 这样就是错误的。 其中,()表示函数调用。同时可以看到,interface只是约束类型,并不约束变量名。...

  • JS函数调用

    js 里函数调用有4种模式:方法调用、正常函数调用、构造器函数调用、apply/call 调用。无论哪种函数调用除...

  • [转载]JavaScript权威指南(8)--函数

    文章前言 一 函数定义 二 函数调用 1,函数调用 2,方法调用 3,构造函数调用 4,间接调用 三 函数的实参和...

  • 【Solidity学习笔记】外部函数的调用

    3.8 外部函数的调用 在Solidity中,有两种函数调用:内部函数调用和外部函数调用。内部函数调用是指一个函数...

  • js里函数调用的四种模式

    js 里函数调用有4种模式:方法调用、正常函数调用、构造器函数调用、apply/call调用。同时,无论哪种函数调...

  • Nodejs学习笔记-函数

    调用本地函数调用外部函数-支持一个函数调用外部函数-支持多个函数字符串方式调用函数 代码:https://gith...

  • 为何C++类成员函数形参后面会加const

    在类成员函数中,有部分函数会在函数形参后面加入const作为该函数的约束,将该函数约束为常量函数,假设所属...

  • 内联函数

    解决函数调用效率的问题:函数之间调用,是内存地址之间的调用,当函数调用完毕之后还会返回原来函数执行的地址。函数调用...

  • Python学习(二)

    函数 python内置了很多函数可以直接被调用,可以从官方文档查阅内置的函数. 函数调用 调用abs()函数 函数...

  • JavaScript函数的四种调用模式

    函数调用模式 单独独立调用的就是函数:函数名(参数) 任何自调用函数都是函数模式 this表示全局变量 方法调用模...

网友评论

      本文标题:函数调用约束

      本文链接:https://www.haomeiwen.com/subject/ydbegltx.html