让我先来介绍一下调用约定。
1.什么是调用约定?
一个函数从调用到完成都经历了哪些步骤呐?
1 .调用要使用的函数,把调用者的函数入栈
2 . 把函数的参数压栈或者存储到寄存器
3. 把函数使用到的一些寄存器压栈
4.执行函数
5.处理函数返回值
6.把那些压栈的寄存器的值恢复到原来的值
7.清空那些压栈参数把返回值传递给寄存器
8 返回到调用者函数
而函数的调用约定就是一个规定,规定怎样传参数,谁负责善后,以及修饰函数的名字。
__cdecl | __stdcall | __fastcall | |
---|---|---|---|
参数传递方式 | 栈传递,参数从右向左依次入栈 | 栈传递,参数从右向左依次入栈 | 寄存器和栈传递,函数的第一个参数和第二个参数(从左向右)<=32字节的参数,通过ecx和edx传递,如果有其他参数,则剩余参数从右向左依次传递 |
清理栈方式 | 由调用者负责恢复栈顶指针 | 由被调者负责恢复栈顶指针 | 由被调者负责恢复栈顶指针 |
适用场合 | c/c++,MFC的默认方式 | Win API | 要求速度快 |
2.对于c语言的调用约定
这些是它常用来修饰名字的调用约定
__cdecl | __stdcall | __fastcall |
---|---|---|
__functionname | __functionname@number | @functionname@number |
__cdel :在函数名前面直接加 __
__functionname@number : 在函数名前面加 __,后面跟 @ 参数的字节数
__fastcall : 在函数名前面加 __后面跟@参数字节数
比如说 void add(int a,char b)
依次用它们来修饰之后的结果为
1. __add
2. __add@5
3. @add@5
3.对于c++的调用约定
它的修饰规则为
1.以?标识函数名的开始,后跟函数名
2.
2.1 :当为__cdecall调用约定时,函数名后面跟"@@YA"标识参数表开始
2.2 : 当为__stdcall调用约定,函数名后面跟"@@YG"标识参数表开始
2.3 : 当为__fastcall调用约定,函数名后面跟"@@YI"标识参数表开始
3.参数表的符号表示( 在下面)
4.参数的第一项为该函数的返回类型,其后依次跟参数的数据类型,指针标识在其所指的数据类型前
5.参数表后以"@Z"标识整个名字的结束,如果没有参数,则以Z结束
参数表的符号
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代表一个重复
比如是 void func(int * p1,int *p2,double * p3)
使用的是__cdecall调用约定,那么它的函数修饰名为
?func@@YAXPAH0PAN@Z
4.原因
知道了它们的修饰规则后,答案就显而易见了
对于c语言来说,一般情况下使用的是第一种调用约定,而这种调用约定,只是在函数名前面加了_
比如说有这么两个函数
void fun(int a,int b)
void fun(int a,char b)
无论是第一个函数,还是第二个函数,它们的函数名修饰之后,都会变成_fun,所以这样编译器根本无法区分你调用的是那个函数,所以无法重载
对于c++来说
它一般使用的是第一种调用约定
它可以根据函数的返回值,还有参数类型和个数,把函数名修饰成不同的字符串
void fun(int a,int b) ?func@@YAXHH@Z
void fun(int a,char b) ?func@@YAXHD@Z
所以c++可以根据函数不同的参数数量和类型构成重载
那这样的话,你会不会有一个问题,那就是,返回值不同的话,它的函数名经过修饰后明明不同呀,为什么返回值不同不能构成重载呐?
但是你想这样一个问题
就上面两个函数,当你使用fun(1.2)进行调用的时候,它俩有什么区别吗?
它俩都有两个参数,都是int 类型,所以说都符合(1,2)这个情况,那么就会出现二义性,这样的话编译器就不知道调用那个了,所以说返回值不能构成函数重载的条件。