http://zh.wikipedia.org/wiki/X86%E8%B0%83%E7%94%A8%E7%BA%A6%E5%AE%9A
这里描述了在x86芯片架构上的调用约定(calling conventions)。 调用约定描述了被调用代码的接口:
- 原子(标量)参数,或复杂参数独立部分的分配顺序;
- 参数是如何被传递的(放置在栈上,或是寄存器中,亦或两者混合);
- 被调用者应保存调用者的哪个寄存器;
- 调用函数时如何为任务准备堆栈,以及任务完成如何恢复;
这与编程语言中对于大小和格式的分配紧密相关。另一个密切相关的是名称修饰,这决定了代码中的符号名称如何映射到链接器中的符号名。
调用约定,类型表示和名称修饰这三者的统称,即是总所周知的应用二进制接口(ABI)。
不同编译器在实现这些约定总是有细微的差别存在,所以在不同编译器编译出来的代码很难接合起来。
另一方面,有些约定被当作一种API标准(如stdcall),编译器实现都较为一致。
调用者清理 cdecl syscall optlink
在这些约定中,调用者自己清理栈上的变元(arguments),这样就运行了可变参数列表的实现,如printf()。
cdecl
cdecl(C declaration,即C声明)是源起C语言的一种调用约定,x86架构上的许多C编译器都使用这个约定。
在cdecl中,子例程变元是在栈上传递的。EAX寄存器返回整型值和内存地址,浮点数则是在ST0 x87寄存器上。
EAX, ECX和EDX寄存器是由调用者保存的,其余的寄存器由被调用者保存。(EBX, EBP, ESI, EDI)
当调用一个新函数时,x87浮点寄存器ST0到ST7都必须为空(弹出或释放掉),而且在退出函数时ST1到ST7也必须为空。
在C语言中,函数参数是以相反顺序推入栈的。在GNU/Linux GCC,把这一约定做为事实上的标准。
GCC自4.5版本开始,调用函数时,堆栈上的数据必须以16B对齐(之前的版本只需要4B对齐即可)。
cdecl调用约定通常作为x86 C编译器的默认调用规则,许多编译器也提供了自动切换调用约定的选项。
如果需要手动指定调用规则为cdecl,编译器可能会支持如下语法:
void _cdecl funct();
其中_cdecl修饰符需要在函数原型中给出,在函数声明中会覆盖掉其他的设置。
syscall
与cdecl类似,变元被从右到左推入栈中。EAX, ECX和EDX不会保留值。参数列表的大小被放置在AL寄存器中(?)。
syscall是32位OS/2 API的标准。
optlink
变元也是从右到左被推入栈。从最左边开始的三个字符变元会被放置在EAX, EDX和ECX中,最多四个浮点变元会被传入ST(0)到ST(3)中----
虽然这四个参数的空间也会在参数列表的栈上保留。函数的返回值在EAX或ST(0)中。保留的寄存器有EBP, EBX, ESI和EDI。
optlink在IBM VisualAge编译器中被使用。
被调用者清理 pascal register stdcall fastcall (microsoft, borland )
如果被调用者要清理栈上的参数,需要在编译阶段知道栈上有多少字节要处理。因此,此类的调用约定并不能兼容于可变参数列表,如printf()。
然而,这种调用约定也许会更有效率,因为需要解堆栈的代码不要在每次调用时都生成一遍。
使用此规则的函数容易在asm代码被认出,因为它们会在返回前解堆栈。
x86 ret指令允许一个可选的16位参数说明栈字节数,用来在返回给调用者之前解堆栈。代码类似如下:
ret 12
pascal
基于Pascal语言的调用约定,参数从左至右入栈(与cdecl相反)。被调用者负责在返回前清理堆栈。 此调用约定常见在如下16-bit API中:OS/2 1.x,微软Windows 3.x,以及Borland Delphi版本1.x。
register
Borland fastcall的别名而已。
stdcall
这个一个Pascal调用约定的变体,被调用者依旧负责清理堆栈,但是参数从右往左入栈----与cdecl一致。
寄存器EAX, ECX和EDX被指定在函数中使用,返回值放置在EAX中。
stdcall对于微软Win32 API和Open Watcom C++是标准。
fastcall
此约定还未被标准化,不同编译器的实现也不一致。 典型的fastcall约定会传递一个或多个变元到寄存器上,减少对内存的访问。
Microsoft fastcall
Microsoft或GCC的__fastcall约定(也即__msfastcall)传入头两个变元(从左至右)到ECX和EDX中,剩下的变元从右至左推入栈上。
Borland fastcall
从左至右,传入三个参数至EAX, EDX和ECX中。剩下的参数推入栈,也是从左至右。
在32位编译器Embarcadero Delphi中,这是缺省调用约定,在编译器中以register形式为人知。 在i386上的某些版本Linux也使用了此约定。
调用者或被调用者清理 thiscall
thiscall
在调用C++非静态成员函数时使用此约定。基于所使用的编译器和函数是否使用可变参数,有两个主流版本的thiscall。
对于GCC编译器,thiscall几乎与cdecl等同:调用者清理堆栈,参数从右到左传递。差别在于this指针,thiscall会在最后把指针推入栈中,虽然在函数原型中它是隐式的第一个参数。
在微软Visual C++编译器中,this指针被传到ECX寄存器上,被调用者负责清理堆栈,其余同此编译器的C版本和Windows API函数使用的stdcall约定。
当函数使用可变参数,此时调用者负责清理堆栈(参考cdecl)。 thiscall约定只在微软Visual C++ 2005及其之后的版本被显式指定。
其他编译器中,thiscall并不是一个关键字(反汇编器如IDA使用__thiscall)。
x86-64调用约定
x86-64调用约定得益于更多的寄存器可以用来传参。而且,不兼容的调用约定也更少了,不过还是有2种主流的规则。
微软x64调用约定
微软x64调用约定使用RCX, RDX, R8, R9这四个寄存器传递头四个整型或指针变量(从左到右),
使用XMM0, XMM1, XMM2, XMM3来传递浮点变量。
其他的参数直接入栈(从右至左)。
整型返回值放置在RAX中,浮点返回值在XMM0中。
少于64位的参数并没有做零扩展,此时高位充斥着垃圾。
在Windows x64环境下编译代码时,只有一种调用约定----就是上面描述的约定,也就是说,32位下的各种约定在64位下统一成一种了。
在微软x64调用约定中,调用者的一个职责是在调用函数之前(无论实际的传参使用多大空间),在栈上分配一个32B的“影子空间”;并且在调用之后用弹出此堆栈。
影子空间是用来给RCX, RDX, R8和R9提供溢出空间的(?),即使是对于少于四个参数的函数而言。
例如, 一个函数拥有5个整型参数,第一个到第四个放在寄存器中,第五个就被推到影子空间栈顶上。
当函数被调用,此栈用来组成返回值----影子空间32位+第五个参数。
在x86-64体系下,Visual Studio 2008在XMM6和XMM7中(同样的有XMM8到XMM15)存储浮点数。
结果对于用户写的汇编语言例程,必须保存XMM6和XMM7(x86不用保存这两个寄存器),
这也就是说,在x86和x86-64之间移植汇编例程时,需要注意在函数调用之前/之后,要保存/恢复XMM6和XMM7。
System V AMD64 ABI
此约定主要在Solaris,GNU/Linux,FreeBSD和其他非微软OS上使用。
头六个整型参数放在寄存器RDI, RSI, RDX, RCX, R8和R9上;同时XMM0到XMM7用来放置浮点变元。
对于系统调用,R10用来替代RCX。同微软x64约定一样,其他额外的参数推入栈,返回值保存在RAX中。
与微软不同的是,不需要提供影子空间。在函数入口,返回值与栈上第七个整型参数相邻。
调用约定(pascal,fastcall,stdcall,thiscall,cdecl)区别等
http://blog.csdn.net/maotoula/article/details/6762062
一:函数调用约定;
函数调用约定是函数调用者和被调用的函数体之间关于参数传递、返回值传递、堆栈清除、寄存器使用的一种约定;
它是需要二进制级别兼容的强约定,函数调用者和函数体如果使用不同的调用约定,将可能造成程序执行错误,必须把它看作是函数声明的一部分;
二:常见的函数调用约定;
VC6中的函数调用约定;
调用约定 堆栈清除 参数传递
__cdecl 调用者 从右到左,通过堆栈传递
__stdcall 函数体 从右到左,通过堆栈传递
__fastcall 函数体 从右到左,优先使用寄存器(ECX,EDX),然后使用堆栈
thiscall 函数体 this指针默认通过ECX传递,其它参数从右到左入栈
__cdecl是C/C++的默认调用约定; VC的调用约定中并没有thiscall这个关键字,它是类成员函数默认调用约定;
C/C++中的main(或wmain)函数的调用约定必须是__cdecl,不允许更改;
默认调用约定一般能够通过编译器设置进行更改,如果你的代码依赖于调用约定,请明确指出需要使用的调用约定;
Delphi6中的函数调用约定;
调用约定 堆栈清除 参数传递
register 函数体 从左到右,优先使用寄存器(EAX,EDX,ECX),然后使用堆栈
pascal 函数体 从左到右,通过堆栈传递
cdecl 调用者 从右到左,通过堆栈传递(与C/C++默认调用约定兼容) stdcall 函数体 从右到左,通过堆栈传递(与VC中的__stdcall兼容) safecall 函数体 从右到左,通过堆栈传递(同stdcall)
Delphi中的默认调用约定是register,它也是我认为最有效率的一种调用方式,而cdecl是我认为综合效率最差的一种调用方式;
VC中的__fastcall调用约定一般比register效率稍差一些;
C++Builder6中的函数调用约定;
调用约定 堆栈清除 参数传递
__fastcall 函数体 从左到右,优先使用寄存器(EAX,EDX,ECX),然后使用堆栈 (兼容Delphi的register)
register 函数体 从左到右,优先使用寄存器(EAX,EDX,ECX),然后使用堆栈 (兼容Delphi的register)
__pascal 函数体 从左到右,通过堆栈传递
__cdecl 调用者 从右到左,通过堆栈传递(与C/C++默认调用约定兼容) __stdcall 函数体 从右到左,通过堆栈传递(与VC中的__stdcall兼容) __msfastcall 函数体 从右到左,优先使用寄存器(ECX,EDX),然后使用堆栈(兼容VC的__fastcall)
常见的函数调用约定中,只有cdecl约定需要调用者来清除堆栈;
C/C++中的函数支持参数数目不定的参数列表,比如printf函数;由于函数体不知道调用者在堆栈中压入了多少参数,
所以函数体不能方便的知道应该怎样清除堆栈,那么最好的办法就是把清除堆栈的责任交给调用者; 这应该就是cdecl调用约定存在的原因吧;
VB一般使用的是stdcall调用约定;(ps:有更强的保证吗)
Windows的API中,一般使用的是stdcall约定;(ps: 有更强的保证吗)
建议在不同语言间的调用中(如DLL)最好采用stdcall调用约定,因为它在语言间兼容性支持最好;
三:函数返回值传递方式
其实,返回值的传递从处理上也可以想象为函数调用的一个out形参数; 函数返回值传递方式也是函数调用约定的一部分;
有返回值的函数返回时:一般int、指针等32bit数据值(包括32bit结构)通过eax传递,(bool,char通过al传递,short通过ax传递),
特别的__int64等64bit结构(struct) 通过edx,eax两个寄存器来传递(同理:32bit整形在16bit环境中通过dx,ax传递);
其他大小的结构(struct)返回时把其地址通过eax返回;(所以返回值类型不是1,2,4,8byte时,效率可能比较差)
参数和返回值传递中,引用方式的类型可以看作与传递指针方式相同;
float/double(包括Delphi中的extended)都是通过浮点寄存器st(0)返回;
1.__cdecl
所谓的C调用规则。按从右至左的顺序压参数入栈,由调用者把参数弹出栈。切记:对于传送参数的内存栈是由调用者来维护的。
返回值在EAX中因此,对于象printf这样变参数的函数必须用这种规则。编译器在编译的时候对这种调用规则的函数生成修饰名的饿时候,仅在输出函数名前加上一个下划线前缀,格式为_functionname。
2.__stdcall
按从右至左的顺序压参数入栈,由被调用者把参数弹出栈。_stdcall是Pascal程序的缺省调用方式,通常用于Win32 Api中,切记:函数自己在退出时清空堆栈,返回值在EAX中。
__stdcall调用约定在输出函数名前加上一个下划线前缀,后面加上一个“@”符号和其参数的字节数,格式为_functionname@number。如函数int func(int a, double b)的修饰名是_func@12。
3.__fastcall
__fastcall调用的主要特点就是快,因为它是通过寄存器来传送参数的(实际上,它用ECX和EDX传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈)。__fastcall调用约定在输出函数名前加上一个“@”符号,后面也是一个“@”符号和其参数的字节数,格式为@functionname@number。
这个和__stdcall很象,唯一差别就是头两个参数通过寄存器传送。注意通过寄存器传送的两个参数是从左向右的,即第一个参数进ECX,第2个进EDX,其他参数是从右向左的入stack。返回仍然通过EAX.
4.__pascal
这种规则从左向右传递参数,通过EAX返回,堆栈由被调用者清除
5.__thiscall
仅仅应用于"C++"成员函数。this指针存放于CX寄存器,参数从右到左压。thiscall不是关键词,因此不能被程序员指定
调用约定可以通过工程设置:Setting...C/C++ Code Generation项进行选择,缺省状态为__cdecl。
函數調用方式: Stdcall Cdecl Fastcall WINAPI CALLBACK PASCAL Thiscall Fortran Syscall Declspec(Naked)
http://www.cnitblog.com/textbox/archive/2010/03/10/64575.html
现代的编程语言的函数竟然有那麽多的调用方式。这些东西要完全理解还得通过汇编代码才好理解。他们各自有自己的特点
其实这些调用方式的差别在主要在一下几个方面
1.参数处理方式(传递顺序,存取(利用盏还是寄存器))
2.函数的结尾处理方式(善后处理 如:栈的恢复由谁恢复? 函数内恢复/还是调用后恢复)
以下是理论:
__cdecl 由调用者平栈,参数从右到左依次入栈 是C和C++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,
所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。VC将函数编译后会在函数名前面加上
下划线前缀。是MFC缺省调用约定
__stdcall ,WINAPI,CALLBACK ,PASCAL 由被调用者平栈,参数从右到左依次入栈 ._stdcall是Pascal程序的缺省调用方式,
通常用于Win32 Api中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。VC将函数编译后会在函数名前面加上下划
线前缀,在函数名后加上"@"和参数的字节数
__fastcall 由被调用者平栈,参数先赋值给寄存器,然后入栈 “人”如其名,它的主要特点就是快,因为它是通过寄存器来传送参数的
(实际上,它用ECX和EDX传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前
清理传送参数的内存栈),在函数名修饰约定方面,它和前两者均不同.
_fastcall方式的函数采用寄存器传递参数,VC将函数编译后会在函数名前面加上"@"前缀,在函数名后加上"@"和参数的字节数。
__thiscall 由被调用者平栈,参数入栈,this 指针赋给 ecx 寄存器 仅仅应用于“C++”成员函数。this指针存放于CX寄存器,参数从右
到左压。thiscall不是关键词,因此不能被程序员指定。
__declspec(naked) 这是一个很少见的调用约定,一般程序设计者建议不要使用。编译器不会给这种函数增加初始化和清理代码,
更特殊的是,你不能用return返回返回值,只能用插入汇编返回结果。这一般用于实模式驱动程序设计.
以下是实践:
int __stdcall test_stdcall(char para1, char para2) { para1 = para2; return 0; } int __cdecl test_cdecl(char para, ) { char p = ' '; va_list marker; va_start( marker, para ); while( p != '