概述
函数调用约定
这些调用约定一般情况下会用在跨平台的编译器上,因为不同的编译器对函数的调用约定也不同!
目前在C/C++里有4种函数调用约定,
先从C语言开始介绍:
一.__stdcall 这个属于C语言里默认的函数调用约定!
函数调用约定:在调用指定函数时函数的参数入栈顺序是从右往左依次入栈!并且在传递参数数据时也是通过堆栈来传递,所开辟的栈空间由函数自己自动释放,函数中开辟的堆需要程序员手动释放!返回值会放到EAX寄存器中!该调用约定不支持可变参数!
__stdcall关键字只存在于Microsoft Visual C/C++编译器里,在GNU/GCC编译器里为:_attribute__((stdcall))。
_attribute__在GCC编译器里用于设置函数调用约定属性,变量属性,类型属性,这里不做详细介绍!
二.__cdecl 同样也属于C语言里默认的函数调用约定!
函数调用约定:与__stdcall调用约定一样参数都是以从右往左依次入栈,并且在传递参数数据时也是通过堆栈来传递,不过与__stdcall相反的时__cdecl约定的函数结束时不会自动的去释放栈,而是由调用函数来释放(下面做比较时详细说明),函数中开辟的堆空间也同样不会自动释放,要程序员手动释放!返回值也会放到EAX寄存器中!
函数名修饰:与__stdcall一样!
下面拿这两个函数做一个比较
1. __stdcall是新标准的C/C++调用约定,__cdecl属于标准C/C++调用约定!
2. __stdcall函数调用约定属于自动释放栈空间,__cded函数调用约定属于手动释放栈空间
这里来解释一下什么是自动释放栈空间与手动释放栈空间的区别:
自动释放栈空间:调用函数在调用被调用函数时(__stdcall调用约定)释放栈空间的代码保存在被调用函数里,在被调用函数返回时会自动释放内存栈空间!
手工释放栈空间:手工释放与自动释放的区别就是:释放栈空间的代码保留在调用函数里,被调用函数里没有释放栈空间的代码,当被调用函数返回后由调用函数执行释放栈空间代码释放被调用函数栈空间,称为手工释放!
并且在编译器默认缺省情况下会默认使用__cdecl调用约定!
但是在Windows平台下由操作系统的调用的函数则需要是__stdcall,比如:WinMain,消息循环的回调函数等需要操作系统调用的函数必须指定为__stdcall,不过一般我们在开发期间不用显示的指明这些供系统调用的回调函数,编译器会自动为我们指明!
三. __fastcall 属于C语言里快速函数调用约定!
函数调用约定:__fastcall约定的函数入栈顺序也是从右到左,只不过前两个参数是由寄存器ecx和edx来传递(不同的CPU平台上所使用的寄存器不同,从Intel 386开始采用ecx和edx传递现在基本上统一了),使用_fastcall传递的速度要快于一般函数调用约定!
原因:_fastcall声明的调用约定的函数前两个参数传递参数时由寄存器传递,.ecx和edx里当你读取时CPU会直接从这两个寄存器里取,而寄存器CPU是可以直接访问的,所以速度要快于一般的堆栈!当你要改变这两个参数值时CPU会将你修改过后的值再次更新到寄存器里!通过堆栈传递数据要通过北桥芯片协调工作和MMU内存单元来进行逻辑地址/虚拟地址/物理地址之间的转换(详细参考CPU底层工作原理文章),并且通过与北桥协调工作将数据取到寄存器里CPU才能直接操作!
注意使用该调用约定的函数无法做跨编译器接口,为其他编程语言提供接口时的函数不能使用该调用约定,具体参考GCC官方文档!返回值也会放在EAX寄存器当中!
以上几种就是C语言里的默认调用函数
函数名修饰:
函数名修饰的作用是为了在编译器执行汇编这一步时让为该函数分配指定内存大小和参数!
__stdcall
在C语言里的函数修饰符是前面加一个_下划线后面跟着函数名,函数名后面跟一个@符号,@符号后面跟着的是参数总字节大小
_functionname@number
比如 int functi(int x) 所对应的函数名修饰是_functi@4,注意函数名修饰不会修改原函数名的大小写!
__cdecl
在C语言里的函数修饰符是前面加一个_下划线后面跟着函数名后面跟着的是参数总字节大小:int functi(int x)=_functi4
__fastcall
修饰约定与__stdcall一样,只是标示着函数名开始的下划线变成了@: int functi(int x)=
@ functi@4
在C++里也继承了这些默认的函数调用约定,同时C++里的类成员函数有this指针,所以C++为成员函数变量引入了一个__thiscall函数调用约定!这个调用约定仅限于C++类成员上!
一.__thiscall
函数调用约定:
__thiscall 用于类成员函数,且是 C++ 成员函数的默认调用约定,该调用约定不允许使用可变参数。在 __thiscall
下,被调用方清理堆栈(手工清理),参数从右向左被推入堆栈,与此同时,this
指针会被存放到寄存器上。默认情况下this指针存在于堆栈上!
这里说明一点类成员函数默认的调用约定并非__thiscall ,一般情况下默认为__cdecl!
C++函数名修饰(__stdcall):以?做函数名开始标识符,后面跟着函数名,函数名后面以@@YG标示参数表开始
参数表:
x--void ,
d--char,
e--unsigned char,
f--short,
h--int,
i--unsigned int,
j--long,
k--unsigned long,
m--float,
n--double,
_n--bool,
PA—表示指针,如果后面有相同类型的指针连续出现就用0代替,一个0代表重复一次!
参数表第一个类型用于表示返回类型!
最后参数表后面用@Z标示一个函数名修饰的结束!
比如int fun(int *z,float a)
对应的函数名修饰就是:?fun@@YGHPAHM@Z
在比如连续的指针类型int fun(int *z,int *j,int*d)
对应的函数名修饰就是:?fun@@YGHPAH00@Z
C++函数名修饰(__cdecl):修饰规则与__stdcall一样,不同的是标示参数表开始的标示符: @@YG变成了@@YA
__fastcall
修饰规则与__stdcall一样,不同的是标示参数表开始的标示符: @@YG变成了@@YI
__thiscall:
修饰规则与__cdecl一样!
这些函数的默认调用约定可以在编译器的配置里修改,至于修改方法请根据你的编译器查找对应的修改方法,这里不做详细的介绍!这里说一下C/C++的函数名修饰关系,C语言不需要考虑函数重载所以不需要区分返回类型和参数表,而C++要考虑函数重载的问题需要区分参数表以及返回类型,还有就是动态库的调用,当你用C语言写的动态库让C++调用时由于两个编译器对函数名的修饰不一样所以会出现找不到符号等链接错误的情况,所以要在C++文件中将要读取的函数用extern“C”声明出来,告诉C++编译器链接动态库里这个函数时使用C的方式来链接!
在这里我在重申一遍我上面说的“调用约定一般情况下会用在跨平台的编译器上,因为不同的编译器对函数的调用约定也不同!”
注意这里要明白,DLL是没有自己的独立堆栈的,在运行时会被加载到程序的虚拟空间中,由程序为其分配堆栈,虽然说是由程序为其分配堆栈,实际上是在你调用DLL里的函数时编译器会添加许多加载DLL里函数的汇编代码,这个汇编代码会根据你DLL库里的函数调用约定来为其分配内存,分配内存的约定是由编译器所规定的!
比如最为经典的就是C语言的函数调用约定与C++的调用约定,双方的函数名修饰不同在链接时会出现找不到符号等链接错误的问题,所以需要extern “C”来指明C++用C的方式来链接!
最后这里在说一个调用约定:__pascal这个函数调用约定是源于pascal语言里的默认调用约定,入栈顺序是从左到右,栈释放由调用者释放(手工释放),返回值存放于EXA中,在VC++5.0以上这个调用约定已经被删除!
最后
以上就是喜悦帅哥为你收集整理的C/C++函数调用约定的全部内容,希望文章能够帮你解决C/C++函数调用约定所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复