C/C++调用约定

来源:互联网 发布:房产营销软件下载 编辑:程序博客网 时间:2024/05/16 15:10

                             C/C++函数调用约定

在编程中,一个函数完整的执行需要经过编译链接等多个过程,而在每个过程中编译器都需要为程序提供不同的服务,那么一个函数的调用执行到底需要几个过程呢?下面我们先通过一个函数栈帧的创建看看。

#define _CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>

#include<stdlib.h>

int Add(int left,int right)

{

return left +right;

}

int main()

{

int a, b;

int ret = 0;

scanf("%d%d", &a, &b);

ret = Add(a, b);

system("system");

return 0;

}

   在这个程序中,我们来通过Add()函数的内部汇编代码来看看整个函数的执行过程:

 

下面我们用语言描述一下整个过程:

1. 为函数开辟空间

2. 初始化已开辟空间
3. 把函数参数压栈 
4. 执行函数 
5. 处理函数返回值 
6. 对于第3步中压栈的那些寄存器,恢复它们原来的值 
7. 根据不同的调用约定,清除第1步中压栈的参数,然后返回,或者先返回然后清除。 
    可以看到第6步是第3步的逆操作,而第7步是第12步的逆操作,在第3步中对函数的参数进行压栈,那么当参数个数多于一个时,编译器会按照什么顺序把参数压入栈的?而在第67步中又是怎么把堆栈恢复原装?这就引出了我们今天要讨论的题目——调用约定。

首先我们先来看看常用的几种调用约定:

C语言中有: __cdecl__stdcall__fastcallnaked__pascal

C++中有: __cdecl__stdcall__fastcallnaked__pascal_thiscall

VC 中默认调用是 __cdecl 方式,Windows API 使用 __stdcall 调用方式,在 DLL 导出函数中,为了跟Windows API 保持一致,建议使用 __stdcall 方式。

 VC中,可以设置默认的调用约定,设置路径为:Project à Properties à Configuration Properties à   C/C++ à   Advanced à  call conversion

  下面我们就来详细介绍一下这六种调用约定:

    1__cdecl

__cdecl调用约定又称为 C 调用约定,是 C/C++ 语言缺省的调用约定。参数按照从右至左的方式入栈,函数本身不清理栈,此工作由调用者负责,返回值在EAX中。由于由调用者清理栈,所以允许可变参数函数存在,如int sprintf(char* buffer,const char* format,...);。

 

2、__stdcall

__stdcall 很多时候被称为 pascal 调用约定。pascal 语言是早期很常见的一种教学用计算机程序设计语言,其语法严谨。参数按照从右至左的方式入栈,函数自身清理堆栈,返回值在EAX中。

 

3、__fastcall

顾名思义,__fastcall 的特点就是快,因为它通过 CPU 寄存器来传递参数。他用 eax 和 edx 传送前两个双字(DWORD)或更小的参数,剩下的参数按照从右至左的方式入栈,函数自身清理堆栈,返回值在 EAX 中。

 

4、naked

naked 是一个很少见的调用约定,一般不建议使用。编译器不会给这种函数增加初始化和清理代码,更特殊的是,你不能用return返回返回值,只能用插入汇编返回结果,此调用约定必须跟 __declspec 同时使用。例如定义一个求和程序,如__declspec(naked) int  add(int a,int b);。

 

5、__pascal

这是 pascal 语言的调用约定,跟 __stdcall 一样,参数按照从右至左的方式入栈,函数自身清理堆栈,返回值在eax中。VC 中已经废弃了这种调用方式,因此在写 VC 程序时,建议使用 __stdcall 代替。

 

6、__thiscall

这是 C++ 语言特有的一种调用方式,用于类成员函数的调用约定。如果参数确定,this 指针存放于 ecx 寄存器,函数自身清理堆栈;如果参数不确定,this指针在所有参数入栈后再入栈,调用者清理栈。__thiscall 不是关键字,程序员不能使用。参数按照从右至左的方式入栈。

                

 

0 0
原创粉丝点击