日常知识点之遗留问题梳理(有个面试题关于函数调用约定)

面试过程中遇到一个题,出了一个函数指针,让我写出函数调用过程中,栈的存储顺序。

当时遇到这个问题有点懵,不知道考察到的技术点,后面回顾一下,应该就是这个函数调用约定。

(迷茫不知所措的时候别慌,把遗留问题一个个处理,以及整理,总会见到阳光。)

在这里知道了这个问题的考点:史上最全C/C++面试、C++面经八股文,一文带你彻底搞懂C/C++面试、C++面经!_c++八股-优快云博客

0:总结问题

寄存器的概念以及线程切换时对寄存器是如何处理的。

如何通过寄存器管理以及调用代码的?

需要简单了解不同寄存器的作用,以及如何配合工作。

掌握反汇编查看源码分析问题的手段

1:什么是函数调用约定?

函数调用约定就是对函数调用的一个约束和规定,描述了函数参数是怎么传递和由谁清除堆栈的

​ 1:它决定了,函数参数传递的方式(是否采用寄存器传递参数,采用哪个寄存器传递参数,参数压栈的顺序等),

​ 2:函数调用结束后栈指针由谁恢复(被调用的函数恢复还是调用者恢复),函数修饰名的产生方法。

__stdcall:是standardcall的缩写,是C++的标准调用方式,

​ 规则如下:所有参数从右到左依次入栈,如果是调用类成员的话,最后一个入栈的是this指针。

​ 被调用函数自动清理堆栈,返回值在EAX。

​ 函数修饰名约定:VC将函数编译后会在函数名前面加上下划线前缀,在函数名后加上“@”和参数的字节数。

__cdecl:是C DECLaration的缩写(declaration,声明),表示C语言的默认函数调用方法,

​ 规定如下:所有参数从右往左依次入栈,所有参数由调用者清除,称为手动清栈。返回值在EAX中。

​ 函数修饰名约定:VC将函数编译后会在函数名前面加上下划线前缀,由于由调用者清理栈,所以允许可变参数函数存在。

__fastcall:是快速调用约定,通过寄存器来传送参数,

​ 规则如下:用ECX和EDX传送前两个双字(DWORD)或更小的参数,剩下的参数仍然自右向左压栈传送。

​ 被调用函数在返回前清理传送参数的内存栈,返回值在EAX中。

​ 函数修饰名约定:VC将函数编译后会在函数名前面加上“@”前缀,在函数名后加上“@”和参数的字节数。

thiscall:是唯一一个不能明确指明的函数修饰符,thiscall只能用于处理C++类成员函数的调用,同时thiscall也是C++成员函数缺省的调用约定,由于成员函数调用还有一个this指针,因此必须特殊处理,

​ 规定如下:采用栈传递参数,参数从右向左入栈,如果参数个数确定,this指针通过TCX传递给被调用者,如果参数个数不确定,this指针在所有参数压栈后被压入堆栈。对参数个数不确定的,调用者清理堆栈,否则由被调函数清理堆栈,__thiscal不是关键字,程序员不能使用。

__pascal:与__stdcall一样,在VC中已经被废弃

2:还是chatgpt更容易理解:(寄存器传参,栈传参两种方式)

函数调用约定(Calling Convention)是指函数如何传递参数、如何返回值,以及如何管理栈和寄存器等细节的规则。不同的平台和编译器可能会有不同的函数调用约定,但其核心目标都是保证调用者和被调用者之间正确地交换数据和控制权。

函数调用约定的主要内容包括:

  1. 参数传递顺序:即函数参数如何传递到被调用函数。
  2. 返回值的传递:函数的返回值如何从被调用函数传递给调用者。
  3. 寄存器的使用:哪些寄存器是调用者保存的,哪些是被调用者保存的,哪些是用来传递参数或返回值的。
  4. 栈的管理:如何分配和释放栈空间,谁负责清理栈。

常见的函数调用约定:

  1. cdecl(C Declaration)
    • 参数传递顺序从右到左,将参数压入栈中。
    • 返回值:返回值通过 EAX 寄存器传递。
    • 谁清理栈:调用者负责清理栈。
    • 应用场景:大多数 C 编译器(如 GCC)使用 cdecl。
  2. stdcall(Standard Calling Convention)
    • 参数传递顺序从右到左,将参数压入栈中。
    • 返回值:返回值通过 EAX 寄存器传递。
    • 谁清理栈:被调用者负责清理栈。
    • 应用场景:主要用于 Windows API。
  3. fastcall
    • 参数传递顺序:前几个参数(通常是前两个或三个)通过寄存器传递(通常是 ECX 和 EDX),其余的通过栈传递。
    • 返回值:返回值通过 EAX 寄存器传递。
    • 谁清理栈:调用者负责清理栈。
    • 应用场景:一些高效调用的场合,尤其是 Windows 和一些现代编译器。
  4. thiscall(主要用于 C++ 成员函数)
    • 参数传递顺序:第一个参数(this 指针)通过寄存器传递,其他参数通过栈传递。
    • 返回值:返回值通过 EAX 寄存器传递。
    • 谁清理栈:由被调用者负责清理栈。
    • 应用场景:C++ 类成员函数调用。
  5. arm calling convention(ARM 架构)
    • 参数传递顺序:前四个参数通过寄存器 R0-R3 传递,其他参数通过栈传递。
    • 返回值:返回值通过 R0 寄存器传递。
    • 谁清理栈:调用者负责清理栈。
    • 应用场景:ARM 架构的函数调用约定。
  6. x86_64 calling convention(64位架构的调用约定)
    • 参数传递顺序:前六个整数参数通过寄存器传递(RDI, RSI, RDX, RCX, R8, R9),其余参数通过栈传递。
    • 返回值:返回值通过 RAX 寄存器传递。
    • 谁清理栈:调用者负责清理栈。
    • 应用场景:64 位系统上的函数调用约定。

调用约定的选择

  • 性能:使用寄存器传递参数通常比通过栈传递要高效,因为访问寄存器比访问内存要快。
  • 兼容性:不同的操作系统和编译器可能使用不同的调用约定,需要根据目标平台的规范选择合适的调用约定。

总结

函数调用约定的设计目的就是确保函数调用过程中,参数能够正确传递,返回值能够正确传递,并且栈空间能够得到正确管理。不同的平台和编译器可能会有不同的调用约定,因此理解并遵循特定环境下的调用约定是编程中很重要的一部分。

3:参考b站大神介绍了解过程。

__cdecl(C Declaration): 参数传递由右向左,由堆栈传递(push),堆栈平衡由主函数控制

__stdcall:参数传递由右向左,由堆栈传递(push),堆栈平衡由自身控制

__fastcall:由寄存器传递参数,从右向左,不需要平衡。 (参数过多,寄存器不够用,会用栈传递)

首先了解大概寄存器的功能:

在 x86 架构中,常见的寄存器有以下几类:

  1. 通用寄存器

    • eax, ebx, ecx, edx 等。这些寄存器可以用于存储数据或执行运算。
    • 在函数调用中,eax 常用于返回值,ecx, edx 等可用于传递参数。
  2. 指针寄存器

    • esp(栈指针)和 ebp(基指针)用于栈的管理。esp 指向栈顶,ebp 用于访问当前函数的栈帧。
  3. 标志寄存器

    • eflags 寄存器保存着程序的状态标志(如零标志、溢出标志等),这些标志用于控制程序的流程。
  4. 段寄存器

    • 这些寄存器(如 cs, ds, ss)用于控制不同段的内存访问,虽然在现代程序中使用较少,但它们在操作系统的内存管理中仍然有重要作用。

    3.1:__cdecl的简单验证逻辑(通过vs查看反汇编源码进行了解)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

esp地址是相反的,+8其实就是出栈(因为传递了两个参数,4字节,这里的字节大小根据参数大小确定)。 管理的是栈指针吧

esp寄存器有大小限制,2^32字节

在这里插入图片描述

3.2:__stdcall的简单验证逻辑

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3.3 __fastcall的简单验证逻辑

在这里插入图片描述

在这里插入图片描述

4:最后,了解一下寄存器的概念和进程/线程切换。

寄存器只有一个,每个进程都有自己寄存器状态,通过操作系统进行管理,相关状态保存在进程/线程控制块(TCB)数据结构中,进行线程切换,恢复。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值