函数调用方式

这篇博客详细解析了C语言中不同函数调用约定,包括fastcall、cdecl、stdcall和thiscall。fastcall通过ecx和edx传递前两个参数,其余压栈;cdecl由调用者清理栈,参数从右到左压栈;stdcall由被调用者清理栈,参数同样从右到左压栈;thiscall主要用于C++成员函数,this指针通过ecx传递,参数处理方式根据个数不同而变化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

#include <stdio.h>
#include <stdlib.h>
//通过栈传入值的话
void _fastcall funcFast(int a, int b)
{
001C1B2F  pop         ecx  
001C1B30  mov         dword ptr [b],edx  
001C1B33  mov         dword ptr [a],ecx  
	printf("%d", a+b);
001C1B36  mov         eax,dword ptr [a]  
001C1B39  add         eax,dword ptr [b]  
001C1B3C  mov         esi,esp  
001C1B3E  push        eax  
001C1B3F  push        1CE988h  
001C1B44  call        dword ptr ds:[1D2470h]  
001C1B4A  add         esp,8  
001C1B4D  cmp         esi,esp  
001C1B4F  call        __RTC_CheckEsp (01C13CFh)  
}
001C1B54  pop         edi  
001C1B55  pop         esi  
001C1B56  pop         ebx  
001C1B57  add         esp,0D8h  
001C1B5D  cmp         ebp,esp  
001C1B5F  call        __RTC_CheckEsp (01C13CFh)  
001C1B64  mov         esp,ebp  
001C1B66  pop         ebp  
001C1B67  ret  
001C1B68  int         3  
。。。。。
001C1B7F  int         3  
void _cdecl funccde(int a,int b)
{
001C1B80  push        ebp  //栈帧已经变化,esp指向最高,跟随压入的在动
001C1B81  mov         ebp,esp  
001C1B83  sub         esp,0C0h  
001C1B89  push        ebx  
001C1B8A  push        esi //这三个值的压入是在esp基础上的压入 
001C1B8B  push        edi             //edi目的寄存器,esi源寄存器,用于大块数据的移动
001C1B8C  lea         edi,[ebp-0C0h]  
001C1B92  mov         ecx,30h  
001C1B97  mov         eax,0CCCCCCCCh  
001C1B9C  rep stos    dword ptr es:[edi]  
	printf("%d",a-b);
001C1B9E  mov         eax,dword ptr [a]  
001C1BA1  sub         eax,dword ptr [b]  
001C1BA4  mov         esi,esp  
001C1BA6  push        eax  
001C1BA7  push        1CE988h  
001C1BAC  call        dword ptr ds:[1D2470h]  
001C1BB2  add         esp,8  
001C1BB5  cmp         esi,esp  
001C1BB7  call        __RTC_CheckEsp (01C13CFh)  
}
001C1BBC  pop         edi  //cdelc自己进行栈帧的回退
001C1BBD  pop         esi  //通过堆栈进行传值
001C1BBE  pop         ebx  
001C1BBF  add         esp,0C0h  //此处也有增加 
001C1BC5  cmp         ebp,esp  
001C1BC7  call        __RTC_CheckEsp (01C13CFh)  
001C1BCC  mov         esp,ebp  
001C1BCE  pop         ebp  
001C1BCF  ret                                    //没有进行平衡
001C1BD0  int         3  
。。。
--- e:\c_study\算法\test\test\c.c ------------------------------------------------
void _stdcall funcstd(int a, int b)
{
001C1BF0  push        ebp  
001C1BF1  mov         ebp,esp  
001C1BF3  sub         esp,0C0h  
001C1BF9  push        ebx  
001C1BFA  push        esi  
001C1BFB  push        edi  
001C1BFC  lea         edi,[ebp-0C0h]  
001C1C02  mov         ecx,30h  
001C1C07  mov         eax,0CCCCCCCCh  
001C1C0C  rep stos    dword ptr es:[edi]  
	printf("%d", a*b);
001C1C0E  mov         eax,dword ptr [a]  
001C1C11  imul        eax,dword ptr [b]  
001C1C15  mov         esi,esp  
001C1C17  push        eax  
001C1C18  push        1CE988h  
001C1C1D  call        dword ptr ds:[1D2470h]  
001C1C23  add         esp,8  
001C1C26  cmp         esi,esp  
001C1C28  call        __RTC_CheckEsp (01C13CFh)  
}
001C1C2D  pop         edi  
001C1C2E  pop         esi  
001C1C2F  pop         ebx  
001C1C30  add         esp,0C0h  ///
001C1C36  cmp         ebp,esp  
001C1C38  call        __RTC_CheckEsp (01C13CFh)  
001C1C3D  mov         esp,ebp  
001C1C3F  pop         ebp  
001C1C40  ret         8  //栈顶平衡8
--- 无源文件 -----------------------------------------------------------------------

--- e:\c_study\算法\test\test\c.c ------------------------------------------------
//_thiscall只能出现在非静态成员函数中,通过exc来带参
int main()
{
001C1C60  push        ebp          //保存栈底地址
001C1C61  mov         ebp,esp  
001C1C63  sub         esp,0D8h   //开辟栈帧
001C1C69  push        ebx  
001C1C6A  push        esi  
001C1C6B  push        edi  
001C1C6C  lea         edi,[ebp-0D8h]  
001C1C72  mov         ecx,36h  
001C1C77  mov         eax,0CCCCCCCCh  
001C1C7C  rep stos    dword ptr es:[edi]  
	int a=12;
001C1C7E  mov         dword ptr [a],0Ch  
	int b=11;
001C1C85  mov         dword ptr [b],0Bh  
	funccde(a,b);                      //压入的时候是否在原来的栈帧之上还是有一段距离隔开
001C1C8C  mov         eax,dword ptr [b]  //在调用者函数栈帧中将值压入栈
001C1C8F  push        eax  
001C1C90  mov         ecx,dword ptr [a]  //cdel是通过栈帧传递参数,不定参数可以使用 //压入参数的时候是在esp的基础上压入,然后压入ebp,eip的值,开辟栈帧
001C1C93  push        ecx                //调用方平衡栈
001C1C94  call        _funccde (01C143Dh)  //call指令包含压入下一条指令的地址
001C1C99  add         esp,8   //esp进行偏移
	funcstd(a,b);
001C1C9C  mov         eax,dword ptr [b]  //函数栈进行参数传递,不能使用不定参数
001C1C9F  push        eax                //被调放平衡栈
001C1CA0  mov         ecx,dword ptr [a]  
001C1CA3  push        ecx  
001C1CA4  call        _funcstd@8 (01C13A2h)  //没有esp的偏移 
	funcFast(a,b);
001C1CA9  mov         edx,dword ptr [b]         //edx和ecx进行传递值  
001C1CAC  mov         ecx,dword ptr [a]  		//参数过多就会通过寄存器和栈传参数
001C1CAF  call        @funcFast@8 (01C1249h)     //不能使用不定参数  
	return 0;
001C1CB4  xor         eax,eax  
}//当参数为0时,无需区分调用方式,使用cdecl和stdcall一样
001C1CB6  pop         edi  
001C1CB7  pop         esi  
001C1CB8  pop         ebx  
001C1CB9  add         esp,0D8h  
001C1CBF  cmp         ebp,esp  
}
001C1CC1  call        __RTC_CheckEsp (01C13CFh) //call用来保存返回来时候的下一条指令的地址,ret用来返回,将压入栈的eip值还原到寄存器中  
001C1CC6  mov         esp,ebp  
001C1CC8  pop         ebp   //先将ebp还原,然后ret还原eip寄存器,esp寄存器减低
001C1CC9  ret  
//_cdecl与_stdcall只在参数平衡栈帧上有所不同,其余部分相同
//但经过优化后,_cdecl调用方式的函数在同一作用域内多次使用,会在效率上比_stdcall高一点
//因为_cdecl可以使用复写传播,_stdcall在函数内平衡参数
//_fastcall调用方式只使用ecx和edx,分别传入第一二个参数,其余的通过栈传入
#include <stdio.h>
#include <stdlib.h>

void _fastcall funcFast(int a, int b)
{
	printf("%d", a+b);
}
void _cdecl funccde(int a,int b)
{
	printf("%d",a-b);
}
void _stdcall funcstd(int a, int b)
{
	printf("%d", a*b);
}
//_thiscall只能出现在非静态成员函数中,通过exc来带参
int main()
{
	int a=12;
	int b=11;
	funccde(a,b);
	funcstd(a,b);
	funcFast(a,b);
	return 0;
}
#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0))) //表明asmlinkage是通过栈来传递参数而不通过寄存器来传递参数
#define fastcall  __attribute__((regparm(3)))         //参数不是通过栈传递,而是直接放到寄存器里,被调用函数直接从寄存器取参数
缺省的时候是通过寄存器传递参数

几种函数的调用方式:
1. stdcall
stdcall调用方式的函数声明为: int _stdcall function(int a, int b);
stdcall的调用方式意味着: (1) 参数从右向左依次压入堆栈 (2) 由被调用函数自己来恢复堆栈 (3) 函数名自动加前导下划线,后面紧跟着一个@,其后紧跟着参数的尺寸
上面那个函数翻译成汇编语言将变成:
push b 先压入第二个参数
push a 再压入第一个参数
call function 调用函数
在编译时,此函数的名字被翻译为_function@8

2. cdecl
cdecl调用方式又称为C调用方式,是C语言缺省的调用方式,它的语法为: int _cdecl function(int a, int b) // 明确指定用C调用方式
cdecl的调用方式决定了: (1) 参数从右向左依次压入堆栈 (2) 由调用者恢复堆栈 (3) 函数名自动加前导下划线
由于是由调用者来恢复堆栈,因此C调用方式允许函数的参数个数是不固定的,这是C语言的一大特色。
此方式的函数被翻译为:
push b // 先压入第二个参数
push a // 在压入第一个参数
call funtion // 调用函数
add esp, 8 // 清理堆栈 。。。。。需要熟悉一下esp寄存器的功能,建议看一下汇编有关的书,基本都有讲
在编译时,此方式的函数被翻译成:_function

3. fastcall
fastcall 按照名字上理解就可以知道,它是一种快速调用方式。
此方式的函数的第一个和第二个DWORD参数通过ecx和edx传递, 后面的参数从右向左的顺序压入栈。
被调用函数清理堆栈。
函数名修个规则同stdcall
其声明语法为: int fastcall function(int a, int b);

4. thiscall
thiscall 调用方式是唯一一种不能显示指定的修饰符。它是c++类成员函数缺省的调用方式。由于成员函数调用还有一个this指针,因此必须用这种特殊的调用方式。
thiscall调用方式意味着:
参数从右向左压入栈。
如果参数个数确定,this指针通过ecx传递给被调用者;如果参数个数不确定,this指针在所有参数压入栈后被压入栈。
参数个数不定的,由调用者清理堆栈,否则由函数自己清理堆栈。
可以看到,对于参数个数固定的情况,它类似于stdcall,不定时则类似于cdecl。

附: 链表的泛型编程:
#define list_entry(ptr, type, member) container_of(ptr, type, member)

#define container_of(ptr, type, member) ({            \
        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \ //typeof( ((type *)0)->member )获得该的数据的类型,定义一个变量,初始化
        (type *)( (char *)__mptr - offsetof(type,member) );}) //使用mptr来得到该结构体的起始地址,然后将该变量强转类型

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) //member在该类型中的偏移量

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值