c++中的几种函数调用约定(转)
C++中的函数调用约定(调用惯例)主要针对三个问题:
1、参数传递的方式(是否采用寄存器传递参数、采用哪个寄存器传递参数、参数压桟的顺序等);
参数的传递方式,最常见的是通过栈传递。函数的调用方将参数压入栈中,函数自己再从栈中将参数取出。
对于有多个参数的函数,调用惯例要规定函数调用方将参数压栈的顺序,是从左往右还是从右往左。有些调用惯例还允许使用寄存器传递参数。
2、函数调用结束后的栈指针由谁恢复(被调用的函数恢复还是调用者恢复);
栈的维护方式:在函数将参数压栈之后,函数体 会被调用,此后需要将被压入的参数全部弹出,以使得栈在函数调用前后保持一致。这个弹出工作可以由函数的调用方来完成,也可以由函数本身完成。
3、函数编译后的名称;
名称修饰策略,为了链接的时候对调用惯例进行区分,调用惯例要对函数本身的名字进行修饰。不同的调用惯例有不同的名字修饰策略。
对实例代码有几点说明(使用的平台为vs2012+intel x86架构)
1、栈顶指针即为esp;
2、int型占32字节内存;
3、桟顶为小地址端,栈底为大地址端,因此出栈需要增大esp;
下面对C++中见到的stdcall、cdecl、fastcall和thiscall做简要说明。
1、stdcall
stdcall是standard call的缩写,也被称为pascal调用约定,因为pascal使用的函数调用约定就是stdcall。
使用stdcall的函数声明方式为:int __stdcall function(int a,int b)
stdcall的调用约定意味着:
1)采用桟传递全部参数,参数从右向左压入栈;
2)被调用函数负责恢复栈顶指针 ;
3) 函数名自动加前导的下划线,后面是函数名,之后紧跟一个@符号,其后紧跟着参数的尺寸,例如_function@4;
下面给出实例:
- int _stdcall funb(int p,int q) //声明为stdcall方式
- {
- return p-q;
- }
- e=funb(3,4);
- 012C42F7 push 4 //参数q入栈
- 012C42F9 push 3 //参数p入栈
- 012C42FB call funb (012C1244h) //调用函数
- 012C4300 mov dword ptr [e],eax //调用者没有处理esp
函数编译后的汇编代码为:
- int _stdcall funb(int p,int q)
- {
- 012C3D80 push ebp
- 012C3D81 mov ebp,esp //将esp保存入ebp中
- 012C3D83 sub esp,0C0h
- 012C3D89 push ebx
- 012C3D8A push esi
- 012C3D8B push edi
- 012C3D8C lea edi,[ebp-0C0h]
- 012C3D92 mov ecx,30h
- 012C3D97 mov eax,0CCCCCCCCh
- 012C3D9C rep stos dword ptr es:[edi]
- return p-q;
- 012C3D9E mov eax,dword ptr [p]
- 012C3DA1 sub eax,dword ptr [q]
- }
- 012C3DA4 pop edi
- 012C3DA5 pop esi
- 012C3DA6 pop ebx
- 012C3DA7 mov esp,ebp
- 012C3DA9 pop ebp
- 012C3DAA ret 8 //注意此处,用被调函数负责恢复esp
以上面函数为例,参数q首先被压栈,然后是参数p(参数从右向左入栈),然后利用call调用函数,
而在编译时,这个函数的名字被翻译成_funb@8,其中8代表参数为8个字节(2个int型变量)。
另外,stdcall可以用于类成员函数的调用,这种情况下唯一的不同就是,所有参数从右向左依次入栈后,this指针会最后一个入栈。下面给出示例。
- class A
- {
- public:
- A(int a)
- {
- this->val=a;
- }
- int _stdcall fun(int par) //类成员函数采用stdcall
- {
- return val-par;
- }
- private:
- int val;
- };
函数调用代码如下:
- A t(3);
- int d,e,f,g;
- g=t.fun(4);
函数调用代码编译后为:
- g=t.fun(4);
- 00DB4317 push 4 //参数4入栈
- 00DB4319 lea eax,[t]
- 00DB431C push eax //this指针入栈,下面会验证eax内容即为A的对象的地址
- 00DB431D call A::fun (0DB1447h)
- 00DB4322 mov dword ptr [g],eax
编译后的代码为:
- int _stdcall fun(int par)
- {
- 00DB3CF0 push ebp
- 00DB3CF1 mov ebp,esp
- 00DB3CF3 sub esp,0C0h
- 00DB3CF9 push ebx
- 00DB3CFA push esi
- 00DB3CFB push edi
- 00DB3CFC lea edi,[ebp-0C0h]
- 00DB3D02 mov ecx,30h
- 00DB3D07 mov eax,0CCCCCCCCh
- 00DB3D0C rep stos dword ptr es:[edi]
- return val-par;
- 00DB3D0E mov eax,dword ptr [this]
- 00DB3D11 mov eax,dword ptr [eax]
- 00DB3D13 sub eax,dword ptr [par]
- }
- 00DB3D16 pop edi
- 00DB3D17 pop esi
- 00DB3D18 pop ebx
- 00DB3D19 mov esp,ebp
- 00DB3D1B pop ebp
- 00DB3D1C ret 8 //由被调用函数负责恢复栈顶指针,由于参数为int型变量(4字节)和一个指针(32为,4字节),共8字节
下面验证入栈时eax中的内容为A对象的地址。
入栈时eax内容如下,为0x0035F808。
找到内存中0x0035F808的内容,为3,。
再看main函数中实例化对象的代码。
可见,this指针正是通过eax入栈。
由此可见,用于类成员函数时,唯一的不同就是在参数入栈完毕后,this指针会最后一个入栈。
2、cdecl
cdecl是C Declaration的缩写,又称为C调用约定,是C语言缺省的调用约定,采用这种方式调用的函数的声明是:
int function (int a ,int b) //不加修饰就是采用默认的C调用约定
int _cdecl function(int a,int b) //明确指出采用C调用约定
cdecl调用方式规定:
1、采用桟传递参数,参数从右向左依次入栈;
2、由调用者负责恢复栈顶指针;
3、在函数名前加上一个下划线前缀,格式为_function;
要注意的是,调用参数个数可变的函数只能采用这种方式(如printf)。
下面给出实例。
- int _cdecl funa(int p,int q) //采用cdecl方式
- {
- return p-q;
- }
调用处的代码编译为:
- d=funa(3,4);
- 012C42E8 push 4
- 012C42EA push 3
- 012C42EC call funa (012C1064h) //调用funca
- 012C42F1 add esp,8 //调用者恢复栈顶指针esp
- 012C42F4 mov dword ptr [d],eax //返回值传递给变量d
函数编译后的代码为:
- int _cdecl funa(int p,int q)
- {
- 012C3D40 push ebp
- 012C3D41 mov ebp,esp
- 012C3D43 sub esp,0C0h
- 012C3D49 push ebx
- 012C3D4A push esi
- 012C3D4B push edi
- 012C3D4C lea edi,[ebp-0C0h]
- 012C3D52 mov ecx,30h
- 012C3D57 mov eax,0CCCCCCCCh
- 012C3D5C rep stos dword ptr es:[edi]
- return p-q;
- 012C3D5E mov eax,dword ptr [p]
- 012C3D61 sub eax,dword ptr [q]
- }
- 012C3D64 pop edi
- 012C3D65 pop esi
- 012C3D66 pop ebx
- 012C3D67 mov esp,ebp
- 012C3D69 pop ebp
- 012C3D6A ret //注意此处,被调函数没有恢复esp
因此,stdcall与cdecl的区别就是谁负责恢复栈顶指针和编译后函数的名称问题。
cedcal同样可以用于类成员函数的调用。此时,cdedl与stdcall的区别在于由谁恢复栈顶指针。
类定义如下:
- class A
- {
- public:
- A(int a)
- {
- this->val=a;
- }
- int _cdecl fun(int par) //采用cedcl方式
- {
- return val-par;
- }
- private:
- int val;
- };
调用代码编译如下:
- g=t.fun(4)
- 013D4317 push 4
- 013D4319 lea eax,[t]
- 013D431C push eax //先入栈参数4,后入栈this指针
- 013D431D call A::fun (013D144Ch)
- 013D4322 add esp,8 //由调用者恢复栈顶指针
- 013D4325 mov dword ptr [g],eax
3、fastcall
采用fasecall的函数声明方式为:
int __fastcall function(int a,int b)
fastcall调用约定意味着:
1、函数的第一个和第二个(从左向右)32字节参数(或者尺寸更小的)通过ecx和edx传递,其他参数通过桟传递。从第三个参数(如果有的话)开始从右向左的顺序压栈;
2、被调用函数恢复栈顶指针;
3、在函数名之前加上"@",在函数名后面也加上“@”和参数字节数,例如@function@8;
示例代码如下:
- int __fastcall func(int p,int q,int r) //采用fastcall
- {
- return p-q-r;
- }
调用代码如下:
- f=func(3,4,5);
- 00E74303 push 5 //第三个参数r压桟
- 00E74305 mov edx,4 //p q通过ecx和edx传递
- 00E7430A mov ecx,3
- 00E7430F call func (0E71442h)
- 00E74314 mov dword ptr [f],eax //调用者不负责恢复栈顶指针esp
函数编译后的代码如下:
- int __fastcall func(int p,int q,int r)
- {
- 00E73DC0 push ebp
- 00E73DC1 mov ebp,esp
- 00E73DC3 sub esp,0D8h
- 00E73DC9 push ebx
- 00E73DCA push esi
- 00E73DCB push edi
- 00E73DCC push ecx
- 00E73DCD lea edi,[ebp-0D8h]
- 00E73DD3 mov ecx,36h
- 00E73DD8 mov eax,0CCCCCCCCh
- 00E73DDD rep stos dword ptr es:[edi]
- 00E73DDF pop ecx
- 00E73DE0 mov dword ptr [q],edx
- 00E73DE3 mov dword ptr [p],ecx
- return p-q-r;
- 00E73DE6 mov eax,dword ptr [p]
- 00E73DE9 sub eax,dword ptr [q]
- 00E73DEC sub eax,dword ptr [r]
- }
- 00E73DEF pop edi
- 00E73DF0 pop esi
- 00E73DF1 pop ebx
- 00E73DF2 mov esp,ebp
- 00E73DF4 pop ebp
- }
- 00E73DF5 ret 4 //恢复栈顶指针,由于只有一个参数r被压桟,因此esp+4即可
可以看到,fasecall利用寄存器ecx与edx传递参数,避免了访存带来的开销。适合少量参数提高效率的场合。
4、thiscall
thiscall是唯一一个不能明确指明的函数修饰,因为thiscall只能用于C++类成员函数的调用,同时thiscall也是C++成员函数缺省的调用约定。由于成员函数调用还有一个this指针,因此必须特殊处理。
thiscall意味着:
1、采用桟传递参数,参数从右向左入栈。如果参数个数确定,this指针通过ecx传递给被调用者;如果参数个数不确定,this指针
在所有参数压栈后被压入堆栈;
2、对参数个数不定的,调用者清理堆栈,否则由被调函数清理堆栈
c++中的几种函数调用约定(转)的更多相关文章
- C语言函数调用约定
在C语言中,假设我们有这样的一个函数: int function(int a,int b) 调用时只要用result = function(1,2)这样的方式就可以使用这个函数.但是,当高级语言被编译 ...
- 宏WINAPI和几种调用约定
在VC SDK的WinDef.h中,宏WINAPI被定义为__stdcall,这是C语言中一种调用约定,常用的还有__cdecl和__fastcall.这些调用约定会对我们的代码产生什么样的影响?让我 ...
- 64位只有一种调用约定stdcall
procedure TForm2.Button1Click(Sender: TObject); function EnumWindowsProc(Ahwnd: hwnd; AlParam: lPara ...
- 关于函数调用约定-thiscall调用约定
函数调用约定描述了如何以正确的方式调用某些特定类型的函数.包括了函数参数在栈上的分配顺序.有哪些参数将通过寄存器传入,以及在函数返回时函数栈的回收方式等. 函数调用约定的几种类型 stdcall,cd ...
- C/C++函数调用约定与this指针
关于 C/C++ 函数调用约定,大多数时候并不会影响程序逻辑,但遇到跨语言编程时,了解一下还是有好处的. VC 中默认调用是 __cdecl 方式,Windows API 使用 __stdcall 调 ...
- 函数调用约定_stdcall[转]
关键字 清理堆栈 参数入栈顺序 函数名称修饰(C) __cdecl 调用函数 右 à 左 _函数名 __stdcall 被调用函数 右 à 左 _函数名@数字 __fastcall 被调用函数 右 à ...
- 关于 C/C++ 函数调用约定
关于 C/C++ 函数调用约定,大多数时候并不会影响程序逻辑,但遇到跨语言编程时,了解一下还是有好处的. VC 中默认调用是 __cdecl 方式,Windows API 使用 __stdcall 调 ...
- Android 中调试手段 打印函数调用栈信息
下面来简单介绍下 android 中的一种调试方法. 在 android 的 app 开发与调试中,经常需要用到打 Log 的方式来查看函数调用点. 这里介绍一种方法来打印当前栈中的函数调用关系 St ...
- __cdecl __stdcall __fastcall之函数调用约定讲解
首先讲解一下栈帧的概念: 从逻辑上讲,栈帧就是一个函数执行的环境:函数参数.函数的局部变量.函数执行完后返回到哪里等等. 实现上有硬件方式和软件方式(有些体系不支持硬件栈) 首先应该明白,栈是从高地址 ...
随机推荐
- 第一月多测师讲解_ linux_vim命令_004
一. vi/vim 编辑器共分为三种模式: 命令模式(Command mode),"ESC" 输入模式(Insert mode) 底线命令模式(Last line mode) 命令 ...
- java性能分析之火焰图
原由 最近因为kafka.zookeeper.ES和相关的Java应用的内存问题搞的头大,做运维将近4年,对Java调优.性能方面的知识了解的少之又少,是时候下定决心来对他多一个学习了.不能一口吃成一 ...
- docker 升级后或者重装后,启动容器提示:Error response from daemon: Unknown runtime specified docker-runc
之前安装的版本是docker 1.3,并运行了容器jenkins 现在把docker升级版本为docker-ce 19.03 再使用docker ps发现之前的jenkins容器已经退出了 启动容器: ...
- 在VC6.0下运行C语言程序,以及编程入门必备的常识类小知识!
今天给大家分享在VC6.0环境下编写C语言程序的基本步骤,为初学者打开学习C语言的第一道门.具体步骤如下(如果需要软件资源,可以留言): 1)新建工作区 依次点击 文件--新建--工作区 或是Ctrl ...
- 【树形DP】BZOJ 1131 Sta
题目内容 给出一个\(N\)个点的树,找出一个点来,以这个点为根的树时,所有点的深度之和最大 输入格式 给出一个数字\(N\),代表有\(N\)个点.\(N \le 1000000\).下面\(N-1 ...
- 【Luogu】P3369 【模板】普通平衡树(树状数组)
P3369 [模板]普通平衡树(树状数组) 一.树状数组 树状数组(Binary Indexed Tree(B.I.T), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据结构. ...
- go读取excel表格数据
go读取excel表格数据 使用工具 github.com/Luxurioust/excelize 百度到的都是使用这个 实际上已经改名了 github.com/360EntSecGroup-Skyl ...
- CentOS 6编译安装Redis
[root@localhost ~]# vim /etc/sysconfig/iptables # 添加如下:-A INPUT -m state –state NEW -m tcp -p tcp –d ...
- 如何在construct3上开发游戏&游戏展示
前言 为了更快体验做出游戏的快乐,我们可以直接采用construct3 提供的游戏模板.这里我用的是基础模板中的塔防游戏.我们在这个的基础上加进来"植物大战僵尸"的一些元素,包括内 ...
- 源码都没调试过,怎么能说熟悉 redis 呢?
一:背景 1. 讲故事 记得在很久之前给初学的朋友们录制 redis 视频课程,当时结合了不少源码进行解读,自以为讲的还算可以,但还是有一个非常核心的点没被分享到,那就是源码级调试, 对,读源码还远远 ...