C函数调用与入栈顺序

一.函数修饰符:

函数名字修饰(Decorated Name
方式

    
数的名字修饰(
Decorated Name)就是编译器在编译期间创建的一个字符串,用来指
明函数的定义或原型。
LINK程序或其他工具有时需要指定函数的
名字修饰来定位函数的正确位置。多数情况下程序员并不需要知道函数的名字修饰,
LINK程序或
其他工具会自动区分他们。当然,在某些情况下需要指定函数的名字修饰,例如在
C++程序中,
为了让
LINK程序或其他工具能够匹配到正确的函数名字,就必须为重载函数和一些特殊的
函数(如构造函数和析构函数)指定名字装饰。另一种需要指定函数的名字修饰的情况是在汇编程序中调用
CC++
函数。如果函数名字,调用约定,返回值类型或函数参数有任何改变,原来的名字修饰就不再有效,必须指定新的名字修饰。
CC++
序的函数在内部使用不同的名字修饰方式,下面将分别介绍这两种方式。

1._cdecl  

从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于
“C”函数或者变量,修饰名是在函数名前
加下划线。对于
“C++”函数,有所不同。 

函数
void   test(void)的修饰名是_test
对于不属于一个类的
“C++”全局函数,修饰名是?test@@ZAXXZ 

MFC缺省调用约定。由于是调用者负责把参数弹出栈,所以可以给函数定义个数不定
的参数,如
printf函数。 
2._stdcall  
按从右至
左的顺序压参数入栈,由被调用者把参数弹出栈。对于
“C”函数或者变量,修饰名以下划线为前
缀,然后是函数名,然后是符号
“@”及参数的字节数,如函数int   func(int  
a,   double   b)
的修饰名是_func@12
对于
“C++”函数,则有所不同。 

有的
Win32   API函数都遵循该约定。 
3._fastcall  
头两
DWORD类型或者占更少字节的参数被放入ECXEDX
存器,其他剩下的参数按从右到左的顺序压入栈。由被调用者把参数弹出栈,对于
“C”函数或者
变量,修饰名以
“@”为前缀,然后是函数名,接着是符号“@”
参数的字节数,如函数
int   func(int   a,   double   b)
修饰名是
@func@12。对于“C++”
数,有所不同。
 
未来的编译器可能使用不同的寄存器来存放参数。 
4.thiscall  
仅仅应
用于
“C++”成员函数。this
针存放于
CX寄存器,参数从右到左压栈。thiscall
是关键词,因此不能被程序员指定。
 
5.naked
  call  

采用1-4
调用约定时,如果必要的话,进入函数时编译器会产生代码来保存
ESIEDIEBXEBP
存器,退出函数时则产生代码恢复这些寄存器的内容。
naked   call不产生这样的代
码。
 
naked   call

是类型修饰符,故必须和
_declspec共同使用,如下: 
__declspec(   naked   )   int   func(  
formal_parameters   )  

{  
//   Function   body  
}

<?xml:namespace
prefix = o ns = "urn:schemas-microsoft-com:office:office" /> 

二.函数调用

千万要注意,C
支持默认参数

----------------------------------------------------------------------------------------------------------------

注释:默认参数:比如说下面的函数

int fun(int a,int
b,int c=3)

{

}

c
是指定的默认实参,通常在函数原型中指定。这里给了
3作为默认参数。用平常的时候调用这个函数fun(4,5,6);
么就是
a=4,b=4,c=6。如果这样调用fun(1,2)
么就是
a=1,b=2,c=3,这里c
有指定,因为
c是默认实参,已经有了默认值,这里c
是采用默认值
3

为什么默认实参必须是函数参数表中
最右边的参数。把上面的函数改下

int fun(int
a=3,int b,int c)

{

}

这样调用fun(1,2)
这样就是
a=1,b=2,而c
本就没有赋到值,就出错了。这些参数都是一一对应的。

--------------------------------------------------------------------------------------------------------------------

C/C++
持可变参数个数的函数定义,这一点与
C/C++语言函数参数调用时入栈顺序有
关,

首先引用其他网友的一段文字,来描述函数调用,及参数入栈:
C
支持可变参数的函数
这里的意思是
C支持函数带有可变数量的参数,最常见的例子就

我们十分熟悉的
printf()系列函数。我们还知道在函数调用
时参数是自右向左压栈的
。如果可变参数函数的一般形式是:

    f(p1, p2, p3,
…)

那么参数进栈(以及出栈)的顺序是:
    …
    push p3
    push p2
    push p1
    call f
    pop p1
    pop p2
    pop p3
    …
我可以得到这样一个结论:如果支持可变参数的函数,那么参数进栈的顺序几乎必然是
自右向左
的。并且,参数出栈也不能由函数自己完成,而应该由调用者完成。

这个结论的后半部分是不难理解的,
因为函数自身不知道调用者传入了多少参数,但是

调用者知道,所以调用者应该负责将所有参数
出栈。

在可变参数函数的一般形式中,左边
是已经确定的参数,右边省略号代表未知参数部分。对于已经确定的参数,它在栈上的位置也必须是确定的。否则意味着已经确定的参数


不能定位和找到的,这样是无法保证函数正确执行的。衡量参数在栈上的位置,就是

离开确切的
函数调用点(
call f)有多远。已经确定的参数,它在栈上的位置,不应该

赖参数的具体数量,因为参数的数量是未知的!

所以,选择只能是,已经确定的参
数,离开函数调用点有确定的距离(较近)。满足这

个条件,只有参数入栈遵从自右向左规则。
也就是说,左边确定的参数后入栈,离函数

调用点有确定的距离(最左边的参数最后入栈,离函
数调用点最近)。

这样,当函数开始执行后,它能找到
所有已经确定的参数。根据函数自己的逻辑,它负

责寻找和解释后面可变的参数(在离开调用点
较远的地方),通常这依赖于已经确定的

参数的值(典型的如prinf()
数的格式解释,遗憾的是这样的方式具有脆弱性)。

据说在pascal
参数是自左向右压栈的,与
C的相反。对于pascal
种只支持固定参数函

数的语言,它没有可变参数带来的问题。因此,它选择哪种参数进栈方式都
是可以的。

甚至,其参数出栈是由函数自己完成的,而不是调用者,因为函数的参数的类型和数

是完全已知的。这种方式比采用C
方式的效率更好,因为占用更少的代码量(在
C中,

数每次调用的地方,都生成了参数出栈代码)。

C++
了兼容
C,所以仍然支持函数带有可变的参数。但是在C++
更好的选择常常是函数

重载。

原文链接: https://www.cnblogs.com/forlina/archive/2011/09/12/2174164.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

    C函数调用与入栈顺序

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/32148

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月8日 上午9:24
下一篇 2023年2月8日 上午9:24

相关推荐