编译器原理-函数调用约定/调用规范/传参方式

2024-06-13 13:18

本文主要是介绍编译器原理-函数调用约定/调用规范/传参方式,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

cdecl:使用栈传参,通常使用ax寄存器存放返回值,由调用方重置sp
stdcall:使用栈传参,通常使用ax寄存器存放返回值,由被调用方重置sp
fastcall:约定优先使用寄存器传递参数,其次使用栈,由不同的编译器实现,咱自己也可以实现一个
thiscall:入参的时候多了一个当前对象(this)指针

本文的示例代码在visual studio 2019下写的,下面是一段最简单不过的C代码

int add_function(int a,int b,int c) {return a + b + c;
}int main()
{int aa=add_function(1, 2, 3);
}

cdecl(C Declaration) 约定
将被调用的函数需要的参数,压栈,当被调用的函数执行完毕,调用方负责重置SP的高度
本例中,main方法调用add_function之前,先push 1,2,3,然后调用add_function,add_function执行完毕,由main负责重置SP
下面的代码是mian函数调用add_function前后的一波操作

push        3  
push        2  
push        1  
call        add_function(0371037h)  
add         esp,0Ch
mov        dword ptr [aa],eax  

下面的代码是add_function函数执行前后的一波操作

// 函数序言
push        ebp  
mov         ebp,esp  
sub         esp,0C0h  
// 开始执行a + b + c,并把结果放到eax中
mov         eax,dword ptr [ebp+8]  
add         eax,dword ptr [ebp+0Ch]  
add         eax,dword ptr [ebp+10h] 
// 函数尾声
mov         esp,ebp  
pop         ebp  
ret  

下面把上述两段代码合并到一起
在这里插入图片描述

在这里插入图片描述

以上就是cdecl约定,需要记住的是调用方负责重置SP高度,在本例的代码是main中的add esp,0Ch

stdcall(Standard Call) 约定

ret x指令:将SP-x,逻辑代码为:sp=sp-x

将add_function函数前面加上_stdcall ,编译器会按照stdcall约定编译

int _stdcall add_function(int a,int b,int c) {return a + b + c;
}

编译之后,add_function函数的尾声部分汇编代码如下

mov         esp,ebp  
pop         ebp  
ret         0Ch			 ;此处和cdecl约定不同,cdecl直接ret,而此处ret 0Ch

main函数汇编代码片段如下

push        3  
push        2  
push        1  
call        add_function(07113CAh)  ;此处call之后和cdecl约定不同,cdecl有个add esp,0Ch操作,而此处没有
mov        dword ptr [aa],eax  

通过上述代码已经发现,stdcall约定中重置SP操作是在被调用函数(本例add_function)中做的,这是和cdecl约定不同的地方

fastcall
约定优先使用寄存器传递参数,如果无法通过寄存器,则使用栈传递参数,没有统一实现方式,不同的编译器有不同的策略

thiscall
为面向对象语言设计的调用规范,传参的时候,多出了一个this引用,按照本例的add_function函数来说,例子中是传递3个int,而如果这个函数在一个C++对象中,那么实际传递的参数是4个,多出了一个当前对象的指针,在GCC编译器中使用栈传递这个指针,MSVC中使用ECX寄存器,那么很明显,Java中就是thiscall调用约定,至于重置sp是同cdecl还是stdcall,在vs中是同stdcall的,而java中的ret指令也同样是stdcall

这篇关于编译器原理-函数调用约定/调用规范/传参方式的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1057411

相关文章

Spring Boot读取配置文件的五种方式小结

《SpringBoot读取配置文件的五种方式小结》SpringBoot提供了灵活多样的方式来读取配置文件,这篇文章为大家介绍了5种常见的读取方式,文中的示例代码简洁易懂,大家可以根据自己的需要进... 目录1. 配置文件位置与加载顺序2. 读取配置文件的方式汇总方式一:使用 @Value 注解读取配置方式二

JAVA保证HashMap线程安全的几种方式

《JAVA保证HashMap线程安全的几种方式》HashMap是线程不安全的,这意味着如果多个线程并发地访问和修改同一个HashMap实例,可能会导致数据不一致和其他线程安全问题,本文主要介绍了JAV... 目录1. 使用 Collections.synchronizedMap2. 使用 Concurren

C# foreach 循环中获取索引的实现方式

《C#foreach循环中获取索引的实现方式》:本文主要介绍C#foreach循环中获取索引的实现方式,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录一、手动维护索引变量二、LINQ Select + 元组解构三、扩展方法封装索引四、使用 for 循环替代

将Java程序打包成EXE文件的实现方式

《将Java程序打包成EXE文件的实现方式》:本文主要介绍将Java程序打包成EXE文件的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录如何将Java程序编程打包成EXE文件1.准备Java程序2.生成JAR包3.选择并安装打包工具4.配置Launch4

springboot上传zip包并解压至服务器nginx目录方式

《springboot上传zip包并解压至服务器nginx目录方式》:本文主要介绍springboot上传zip包并解压至服务器nginx目录方式,具有很好的参考价值,希望对大家有所帮助,如有错误... 目录springboot上传zip包并解压至服务器nginx目录1.首先需要引入zip相关jar包2.然

Java数组初始化的五种方式

《Java数组初始化的五种方式》数组是Java中最基础且常用的数据结构之一,其初始化方式多样且各具特点,本文详细讲解Java数组初始化的五种方式,分析其适用场景、优劣势对比及注意事项,帮助避免常见陷阱... 目录1. 静态初始化:简洁但固定代码示例核心特点适用场景注意事项2. 动态初始化:灵活但需手动管理代

Python处理函数调用超时的四种方法

《Python处理函数调用超时的四种方法》在实际开发过程中,我们可能会遇到一些场景,需要对函数的执行时间进行限制,例如,当一个函数执行时间过长时,可能会导致程序卡顿、资源占用过高,因此,在某些情况下,... 目录前言func-timeout1. 安装 func-timeout2. 基本用法自定义进程subp

Spring Boot循环依赖原理、解决方案与最佳实践(全解析)

《SpringBoot循环依赖原理、解决方案与最佳实践(全解析)》循环依赖指两个或多个Bean相互直接或间接引用,形成闭环依赖关系,:本文主要介绍SpringBoot循环依赖原理、解决方案与最... 目录一、循环依赖的本质与危害1.1 什么是循环依赖?1.2 核心危害二、Spring的三级缓存机制2.1 三

C#中async await异步关键字用法和异步的底层原理全解析

《C#中asyncawait异步关键字用法和异步的底层原理全解析》:本文主要介绍C#中asyncawait异步关键字用法和异步的底层原理全解析,本文给大家介绍的非常详细,对大家的学习或工作具有一... 目录C#异步编程一、异步编程基础二、异步方法的工作原理三、代码示例四、编译后的底层实现五、总结C#异步编程

python logging模块详解及其日志定时清理方式

《pythonlogging模块详解及其日志定时清理方式》:本文主要介绍pythonlogging模块详解及其日志定时清理方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录python logging模块及日志定时清理1.创建logger对象2.logging.basicCo