C/C++语言拾遗(六)--函数调用约定

本文详细介绍了C/C++中的函数调用约定,包括stdcall、cdecl、fastcall等,并探讨了不同调用约定对函数参数传递及栈清理的影响。此外,还讨论了函数名称修饰规则、动态链接库处理及如何在VS中设置默认调用约定。

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

C/C++语言拾遗(六)--函数调用约定

一、函数调用约定

当参数个数多于一个时,按照什么顺序把参数压入堆栈函数调用后,由谁来把堆栈恢复原装。在高级语言中,通过函数调用约定来说明这两个问题。常见的调用约定有:

 stdcall,cdecl,fastcall,thiscall,naked call

__stdcall、__cdecl和__fastcall是三种函数调用协议,函数调用协议会影响函数参数的入栈方式、栈内数据的清除方式、编译器函数名的修饰规则等(区别主要体现在编译成的汇编语言上)

__stdcallWindows API默认的函数调用协议。

 __cdeclC/C++默认的函数调用协议。 

__fastcall:适用于对性能要求较高的场合。

二、C语言编译器函数名称修饰规则 

__stdcall:编译后,函数名被修饰为"_functionname@number"。 

__cdecl:编译后,函数名被修饰为"_functionname"。 

__fastcall:编译后, 函数名给修饰为"@functionname@nmuber"。 

注:"functionname"为函数名,"number"为参数字节数。 

注:函数实现和函数定义时如果使用了不同的函数调用协议,则无法实现函数调用。

三、C++语言编译器函数名称修饰规则 

__stdcall:编译后,函数名被修饰为"?functionname@@YG******@Z"。 

__cdecl:编译后,函数名被修饰为"?functionname@@YA******@Z"。 

__fastcall:编译后,函数名被修饰为"?functionname@@YI******@Z"。 

注:"******"为函数返回值类型和参数类型表。

 注:函数实现和函数定义时如果使用了不同的函数调用协议,则无法实现函数调用。 C语言和C++语言间如果不进行特殊处理,也无法实现函数的互相调用。

四、动态链接库的处理

EAPI int add(int a, int b);

其中EAPI的定义如下:

​#ifdef DYNAMIC_EXPORT

​#define _API_ __declspec(dllexport)

​#else

​#define _API_ __declspec(dllimport)

​#endif

​#define EAPI extern "C" _API_

如果定义了DYNAMIC_EXPORT宏,则API表示导出接口,否则表示导入接口;

而 #define EAPIextern “C” API 

表示以C的方式导出(导入)接口。我们在这三个工程中都加入DYNAMIC_EXPORT预编译宏,表示导出接口;而在使用这三个工程(库)的工程(如VisualStudio)中不加DYNAMIC_EXPORT宏,表示导入接口。

五、函数的调用过程

要深入理解函数调用约定,你须要了解函数的调用过程和调用细节。 假设函数A调用函数B,我们称A函数为”调用者”,B函数为“被调用者”。如下面的代码,ShowResult为调用者,add为被调用者。

​int add(int a, int b) 

 return a + b; 

 void ShowResult() 

 std::cout << add(5, 10)<< std::endl; 

}

函数调用过程可以这么描述: 

(1)先将调用者(A)的堆栈的基址(ebp)入栈,以保存之前任务的信息。 

(2)然后将调用者(A)的栈顶指针(esp)的值赋给ebp,作为新的基址(即被调用者B的栈底)。 

(3)然后在这个基址(被调用者B的栈底)上开辟(一般用sub指令)相应的空间用作被调用者B的栈空间。 

(4)函数B返回后,从当前栈帧的ebp即恢复为调用者A的栈顶(esp),使栈顶恢复函数B被调用前的位置;然后调用者A再从恢复后的栈顶可弹出之前的ebp值(可以这么做是因为这个值在函数调用前一步被压入堆栈)。这样,ebp和esp就都恢复了调用函数B前的位置,也就是栈恢复函数B调用前的状态。 这个过程在AT&T汇编中通过两条指令完成,即:

填写图片摘要(选填)

leave 

 ret 

 这两条指令更直白点就相当于:

此部分内容参考:http://blog.youkuaiyun.com/zsy2020314/article/details/9429707

六、​VS设置默认的调用约定

C/C++默认的调用约定是__cdecl,那能不能修改这个默认的调用约定呢? 答案是肯定的。假设你有一个工程名叫VisualStudio,你想让这个工程下的所有函数默认都使用__stdcall,右键工程->Properties->ConfigurationProperties->C/C++->Advanced->Calling Convention,将其设置为__stdcall即可。

参考链接:

https://blog.youkuaiyun.com/luoweifu/article/details/52425733

https://blog.youkuaiyun.com/luoweifu/article/details/52456407

 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值