本文主要是介绍编译器原理-函数调用约定/调用规范/传参方式,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
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
这篇关于编译器原理-函数调用约定/调用规范/传参方式的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!