函数调用、调用约定和名字约定
调用约定:
__cdecl __fastcall与 __stdcall,三者都是调用约定(Calling convention),它决定以下内容:
1)函数参数的压栈顺序,
2)由调用者还是被调用者把参数弹出栈
3)以及产生函数修饰名的方法
__cdecl
__cdecl调用约定又称为C调用约定,是 C/C++ 语言缺省的调用约定。参数按照从右至左的方式入栈,函数本身不清理栈,此工作由调用者负责,返回值在EAX中。
对于 C,__cdecl 命名约定使用前面带下划线 (_) 的函数名;不执行任何大小写转换。对于C++,除非声明为 extern "C",否则 C++ 函数将使用不同的名称修饰方案。
__stdcall
__stdcall调用约定参数按照从右至左的方式入栈,函数自身清理堆栈,返回值在EAX中。
对于 C,__stdcall 命名约定使用前面带下划线 (_) 的函数名,后跟“at”符 (@) 和函数的参数大小(以字节为单位)。不执行任何大小写转换。编译器使用下列命名约定模板:
_functionname@number
__fastcall
顾名思义,__fastcall 的特点就是快,因为它通过 CPU 寄存器来传递参数。他用 ECX 和 EDX 传送前两个双字(DWORD)或更小的参数,剩下的参数按照从右至左的方式入栈,函数自身清理堆栈,返回值在 EAX 中。
__thiscall
这是 C++ 语言特有的一种调用方式,用于类成员函数的调用约定。如果参数确定,this 指针存放于 ECX 寄存器,函数自身清理堆栈;如果参数不确定,this指针在所有参数入栈后再入栈,调用者清理栈。__thiscall 不是关键字,程序员不能使用。参数按照从右至左的方式入栈。
名字修饰约定:
1、修饰名(Decoration name):"C"或者"C++"函数在内部(编译和链接)通过修饰名识别
2、C编译时函数名修饰约定规则:
__stdcall调用约定在输出函数名前加上一个下划线前缀,后面加上一个"@"符号和其参数的字节数,格式为_functionname@number,例如:function(int a, int b),其修饰名为:_function@8
__cdecl调用约定仅在输出函数名前加上一个下划线前缀,格式为_functionname。
__fastcall调用约定在输出函数名前加上一个"@"符号,后面也是一个"@"符号和其参数的字节数,格式为@functionname@number。
3、C++编译时函数名修饰约定规则:
__stdcall调用约定:
1)、以"?"标识函数名的开始,后跟函数名;
2)、函数名后面以"@@YG"标识参数表的开始,后跟参数表;
3)、参数表以代号表示:
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"代表一次重复;
4)、参数表的第一项为该函数的返回值类型,其后依次为参数的数据类型,指针标识在其所指数据类型前;
5)、参数表后以"@Z"标识整个名字的结束,如果该函数无参数,则以"Z"标识结束。
其格式为"?functionname@@YG*****@Z"或"?functionname@@YG*XZ",例如
int Test1(char *var1,unsigned long)----"?Test1@@YGHPADK@Z"
void Test2()-----“?Test2@@YGXXZ”
__cdecl调用约定:
规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的"@@YG"变为"@@YA"。
__fastcall调用约定:
规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的"@@YG"变为"@@YI"。
VC++对函数的省缺声明是"__cedcl",将只能被C/C++调用.
堆栈
栈:栈是向低地址扩展的数据结构,是一块连续的内存的区域。
栈又称堆栈,是用户存放程序临时创建的局部变量,也就是函数括弧“{}”中定义的变量(但不包括static声明的变量,)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进后出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上,可以把堆栈看成一个寄存、交换临时数据的内存区。
栈空间都是在运行时动态的分配的。
函数堆栈在运行时动态分配,原因有二:
一、无法在编译时候确定函数运行时所需的堆栈大小;
二、节省空间
函数名就是一个指针,指向函数入口地址,也就是一个代码段的地址。
在内存中,数据和指令,堆栈分别在不同的地址段,数据段存储的是程序的数据,代码段存储的是程序的指令,栈段是函数调用是保存参数和调用是上下文数据的内存区。栈由系统自动分配.
函数名就指向一个代码段的地址。对数据段的内存地址进行复引用操作(*a),得到内存中数据的值。如果对一个代码段内存地址进行复引用操作,没用什么实际意义,不可能返回一个指令。
在标准c文档(WG14 N1124)里这样描述
A function designator is an expression that has function type. Except when it is the
operand of the sizeof operator or the unary & operator, a function designator with
type "function returning type" is converted to an expression that has type "pointer to
function returning type".
也就是说函数类型表达式(如*pf)会被自动转化成函数指针. 这样你加多少个*都没有关系.
另外对一个函数指针pf, (*pf)() 和 pf() 都已成为合法的用法 (k&R 不支持 pf())。
C99 的文档里也提到这一点:
Pointers to functions may be used either as (*pf)() or as pf(). The latter construct, not
sanctioned in K&R, appears in some present versions of C, is unambiguous, invalidates no old
code, and can be an important shorthand. The shorthand is useful for packages that present only
one external name, which designates a structure full of pointers to objects and functions: member
functions can be called as graphics.open(file) instead of
(*graphics.open)(file).
说的是这样子可以方便调用结构里的函数。