调用规范与可变参数表

语言调用规范是指进行一次函数调用所采用的传递参数的方法,返回值的处理以及调 用堆栈的清理。Microsoft C/C++ 语言中采用了五种调用规范,分别是__cdecl, __stdcall, __fastcall,thiscall和nake每一中调用规范都是利用eax作为返回值,如果 函数返回值是64位的,则利用edx:eax对来返回值。Nake调用规范非常的灵活,足以独立 的一篇文章描述,这里就不再描述nake调用规范。下表列出了前面四种规范调用的特点:

关键字 堆栈清理者 参数传递顺序
__cdecl 调用者 从右至左
__stdcall 被调用者 从右至左
__fastcall 被调用者 从右至左,前两个参数由寄存器ecx,edx传递
thiscall 被调用者或者调用者 从右至左

__cdecl 最大好处在于由于是调用者清理栈,它可以处理可变参数,缺点则在于它增 加了程序的大小,因为在每个调用返回的时候,需要多执行一条清理栈的指令。

__stdcall 是在windows程序设计中出现的最多的调用规则,所有的不可变参数的API 调用都使用这个规则。

__fastcall 在windows内核设计中被广泛的使用,由于两个参数由寄存器直接传递, 采用这种规则的函数效率要比以上两种规则高。

thiscall是C++成员函数的默认调用规范,编译期间,这种调用会根据函数是否支持可 变参数表来决定采用什么方式清理堆栈。如果成员函数不支持可变参数,那么它就是用参 数入栈,ecx保存this指针的方式进行调用,如果成员函数支持可变参数,那么它的调用 和__cdecl类似,唯一不同的是将this指针最后压入栈中进行传递。

调用者和被调用者必须采用同样的规则才能保证程序的正常执行,曾经看到很多程序 员犯的错误就是由于调用规范的不一样,致使程序异常,比如:

DWORD ThreadFunc(LPVOID lpParam)
{
//…
}
CreateThread(..,(LPTHREAD_START_ROUTINE)ThreadFunc, …);

如果在编译期间没有指定编译选项/Gz(指定未指明调用规范的函数采用__stdcall方 式),那么编译器自动将ThreadFunc处理成__cdecl调用规范(/Gd),这样可能在线程开 始的时候正常执行,然而退出的时候由于堆栈没有正常清理,造成访问违例或者非法指令 错误。

以上说了很多清理栈的问题,那么为什么清理栈很重要呢。堆栈是线程相关的,也就 是说每一个线程含有一个堆栈,这个堆栈上保存了局部变量,调用返回地址等很多线程相 关的数据,这也是为什么独立运行的线程可以调用同样一个函数而互不干扰的原因。堆栈 的特点恐怕大家已经非常熟悉了,那么根据上面的每一种调用,我给出一个简单的图示来 说明清理堆栈的重要性,以及为什么上面的例子代码会出错。

图一 这是线程堆栈在运行的时候的样子

时间: 2024-08-04 12:17:51

调用规范与可变参数表的相关文章

n维数组实现(可变参数表的使用)

首先先介绍一下可变参数表需要用到的宏: 头文件:#include<cstdarg> void va_start( va_list arg_ptr, prev_param );  type va_arg( va_list arg_ptr, type );  void va_end( va_list arg_ptr );   va_list:用来保存宏va_start.va_arg和va_end所需信息的一种类型.为了访问变长参数列表中的参数,必须声明              va_list类型

嵌入式 C 语言的可变参数表函数的设计

首先在介绍可变参数表函数的设计之前,我们先来介绍一下最经典的可变参数表printf函数的实现原理.一.printf函数的实现原理在C/C++中,对函数参数的扫描是从后向前的.C/C++的函数参数是通过压入堆栈的方式来给函数传参数的(堆栈是一种先进后出的数据结构),最先压入的参数最后出来,在计算机的内存中,数据有2块,一块是堆,一块是栈(函数参数及局部变量在这里),而栈是从内存的高地址向低地址生长的,控制生长的就是堆栈指针了,最先压入的参数是在最上面,就是说在所有参数的最后面,最后压入的参数在最下

扩展Int 13H调用规范

第一部分简介 一.硬盘结构简介 1.硬盘参数释疑 到目前为止,人们常说的硬盘参数还是古老的CHS(Cylinder/Head/Sector)参数.那么为什么要使用这些参数,它们的意义是什么?它们的取值范围是什么? 很久以前(longlongago...),硬盘的容量还非常小的时候,人们采用与软盘类似的结构生产硬盘.也就是硬盘盘片的每一条磁道都具有相同的扇区数.由此产生了所谓的3D参数(DiskGeometry).即磁头数(Heads),柱面数(Cylinders),扇区数(Sectors),以及

小览call stack(调用栈) (二)——调用约定

在上一篇博客中小览call stack(调用栈) (一)中,我展示了如何在windbg中 观察调用栈的相关信息:函数的返回地址,参数,返回值.这些信息都按照一定 的规则存储在固定的地方.这个规则就是调用约定(calling convention). 调用约定在计算机界不是什么新鲜的概念,已经有许多相关的文献给予详细 的介绍.比较全面的介绍可以参见wikipedia上的相关页面.然而,如果你和我 一样,在第一次接触调用约定的时候,觉得这个概念是个高深神秘的冬冬,那么 就请跟随我一起,在这篇博客中看

面向对象的css:团队协作开发规范和按结构划分模块

文章简介:面向对象的css有两个主要原则:separate the structure from the skin,separate the container from the content.第一个原则体现在模块化思想可以理解为,模块的设计制作和布局框架本身相分离,意味着你的模块不能只为某个布局而编写样式,像微博这类存在换肤功能的产 说起模块化,也许我们首先想到的是编程中的模块设计,以功能块为单位进行程序设计,最后通过模块的选择和组合构成最终产品.把这种思想运用到页面构建中,也已经不是什么新

用Visual C#调用Windows API函数(转)

visual|window|函数 用Visual C#调用Windows API函数 北京机械工业学院研00级(100085)冉林仓       Api函数是构筑Windws应用程序的基石,每一种Windows应用程序开发工具,它提供的底层函数都间接或直接地调用了Windows API函数,同时为了实现功能扩展,一般也都提供了调用WindowsAPI函数的接口, 也就是说具备调用动态连接库的能力.Visual C#和其它开发工具一样也能够调用动态链接库的API函数..NET框架本身提供了这样一种

用Visual C#调用Windows API函数

visual|window|函数 Api函数是构筑Windws应用程序的基石,每一种Windows应用程序开发工具,它提供的底层函数都间接或直接地调用了Windows API函数,同时为了实现功能扩展,一般也都提供了调用WindowsAPI函数的接口, 也就是说具备调用动态连接库的能力.Visual C#和其它开发工具一样也能够调用动态链接库的API函数..NET框架本身提供了这样一种服务,允许受管辖的代码调用动态链接库中实现的非受管辖函数,包括操作系统提供的Windows API函数.它能够定

请前辈们帮我看看注释写得是否规范

问题描述 请前辈们帮我看看注释写得是否规范 解决方案 这种注释对于学习写程序来说的人有用,但是对于真正的程序来说没用.注释不是把每行代码用中文再写一遍,任何程序员都可以得到这些表面的信息.注释应该少而精炼,强调程序的实现意图和接口的调用规范. 解决方案二: 给程序添加注释是帮助别人理解代码 像你这样几乎每行都有注释 反而会增加别人阅读时困难. 注释只需要加载需要加的地方帮助别人理解代码.像下面这种注释根本不需要添加: X x(5); Y(y); Z(z); //实例化对象 解决方案三: 上面说的

为什么基类的析构函数不是虚函数时,就不会调用派生类的析构函数

问题描述 为什么基类的析构函数不是虚函数时,就不会调用派生类的析构函数 我知道,基类的析构函数要生命为虚函数,不然用基类指针删除派生类对象时只会调用基类的析构函数,而不会调用派生类的析构函数,从而发生内存泄露.但是为什么会这样?为什么基类析构函数不是虚函数时,就不会调用派生类析构函数而是虚函数时就会调用派生类析构函数???有没有大神指点一下. 看了几位的回答,都不是我想要的.我已经知道了不声明为虚函数,会造成内存泄露.我想知道为什么会造成内存泄露,原因是什么. 解决方案 1. 析构函数跟普通成员