• Home
  • 写文
  • 关于
    • jlweb Blog photo

      jlweb Blog

      occupied with moon theme of jelly

    • 详情
    • Github
    • Steam
  • 文章
    • 所有文章
    • 所有标签
  • 项目
  • 主站
search clear

C++函数调用方式

10 Dec 2023

阅读时长 ~1 分钟

编辑

1.x86 架构下的函数调用方式

在 x86 架构下,函数调用使用栈来传递参数和保存返回值。具体来说,函数调用的过程如下:

  1. 调用者将函数参数按照从右到左的顺序依次压入栈中。
  2. 调用者将返回地址(即调用完函数后需要返回到的下一条指令的地址)压入栈中。
  3. 调用者跳转到被调用函数的入口地址。
  4. 被调用函数执行完毕后,将返回值存储在 EAX 寄存器中,并将返回地址弹出栈中,跳转回调用者。

需要注意的是,在 x86 架构下,栈是从高地址向低地址增长的,因此函数参数和返回地址的顺序是从右到左依次存储在栈中的。

2.x86-64 架构下的函数调用方式

在 x86-64 架构下,函数调用的方式有一些差异。具体来说,函数调用的过程如下:

  1. 调用者将函数参数按照从左到右的顺序依次存储在寄存器和栈中。前6个参数是从左至右依次存储在 RDI、RSI、RDX、RCX、R8 和 R9 寄存器中,如果参数个数超过 6 个,则将余下的参数按照从右到左的顺序存储在栈中。
  2. 调用者将返回地址(即调用完函数后需要返回到的下一条指令的地址)压入栈中。
  3. 调用者跳转到被调用函数的入口地址。
  4. 被调用函数执行完毕后,将返回值存储在 RAX 寄存器中,并将返回地址弹出栈中,跳转回调用者。

需要注意的是,在 x86-64 架构下,前 6 个参数会存储在寄存器中,而不是像 x86 架构下一样全部存储在栈中。这样可以减少栈操作的次数,提高程序的性能。但是,如果参数个数超过 6 个,仍然需要将余下的参数存储在栈中。 总之,在 x86 和 x86-64 架构下,函数调用方式都使用栈来传递参数和保存返回值,但是在 x86-64 架构下,前 6 个参数会存储在寄存器中,以提高程序的性能。

3.x86 下的调用约定(calling convention)

常见例子汇编 (x86 __fastcall调用例子)

在 x86 架构下,_在 Windows 平台上,Microsoft Visual C++ 编译器默认使用 __stdcall 调用约定,而 GCC 和 Clang 编译器默认使用 cdecl 调用约定。在 Linux 和 macOS 等 UNIX-like 系统上,通常使用的是 cdecl 调用约定。_除了使用栈来传递参数和保存返回值外,还有一些传统的调用约定(calling convention),包括 stdcall、fastcall 、 cdecl和 thiscall 等。这些调用约定主要是用于优化函数调用的性能,具体规则如下:

__stdcall

  • stdcall 是 Windows 操作系统中广泛使用的一种调用约定。在 stdcall 调用约定中,函数参数按照从右到左的顺序依次存储在栈中,函数自身清理堆栈,也就是调用函数内部最后会出现 ret 8, ret 4 退栈。

_fastcall fastcall 是一种常见的调用约定,其目的是减少栈操作的次数,从而提高函数调用的性能。在 fastcall 调用约定中,前两个参数会存储在寄存器 ECX 和 EDX 中,其余参数按照从右到左的顺序依次存储在栈中。函数自身清理堆栈,与 stdcall 相同。fastcall 调用约定通常用于 Windows 中的一些函数库和编译器中。 __cdecl cdecl 是 C 语言中默认的调用约定,也是 Unix 系统中广泛使用的一种调用约定。在 cdecl 调用约定中,函数参数按照从右到左的顺序依次存储在栈中,但是函数返回时由函数调用者清理栈上的参数。因此,cdecl 调用约定比 stdcall 和 fastcall 更加灵活,但是在参数较多时,性能会略低。由于由调用者清理栈,所以允许可变参函数存在。 ___thiscall 这是C++语言特有的一种调用方式,用于类成员函数的调用约定。如果参数确定,this 指针存放于ECX 寄存器,函数自身清理堆栈;如果参数不确定,this 指针在所有参数入栈后再入栈,调用者清理栈。_thiscall 不是关键字,程序员不能使用。参数按照从右至左的方式入栈。

4.x86-64下调用约定

在 x86-64 架构下,函数调用约定主要有 System V 和 Microsoft x64 等,下面分别介绍:

System V

System V 是 x86-64 架构下广泛使用的一种调用约定,它主要用于 Linux、macOS 等操作系统中。在 System V 调用约定中,前 6 个整型或指针类型参数会存储在寄存器 RDI、RSI、RDX、RCX、R8 和 R9 中,其余的参数按照从左到右的顺序依次存储在栈中。函数调用者负责将所有参数压入栈中,并使用 CALL 指令调用函数。被调用函数在返回时会将返回值存储在 RAX 寄存器中,并由调用者负责清除栈上的参数。

Microsoft x64

Microsoft x64 是 x86-64 架构下 Windows 操作系统中广泛使用的一种调用约定。在 Microsoft x64 调用约定中,最左边 4 个位置的整数值参数从左到右分别在 RCX、RDX、R8 和 R9 中传递。 如前所述,第 5 个和更高位置的参数在堆栈上传递。函数调用者负责将所有参数压入栈中,并使用 CALL 指令调用函数。被调用函数在返回时会将返回值存储在 RAX 寄存器中,并由调用者负责清除栈上的参数。

需要注意的是,调用约定只是一种约定,编译器可以根据具体情况使用不同的调用约定。在实际开发中,需要根据具体的操作系统和编译器来选择合适的调用约定。



🥁-CPP Share Tweet +1