C函数调用与入栈顺序

2024-03-31 11:08
文章标签 顺序 函数调用 入栈

本文主要是介绍C函数调用与入栈顺序,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一.函数修饰符:

函数名字修饰(Decorated Name) 方式

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

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类型或者占更少字节的参数被放入ECX和EDX寄 存器,其他剩下的参数按从右到左的顺序压入栈。由被调用者把参数弹出栈,对于“C”函数或者 变量,修饰名以“@”为前缀,然后是函数名,接着是符号“@”及 参数的字节数,如函数int   func(int   a,   double   b)的 修饰名是@func@12。对于“C++”函 数,有所不同。 
未来的编译器可能使用不同的寄存器来存放参数。 
4.thiscall  
仅仅应 用于“C++”成员函数。this指 针存放于CX寄存器,参数从右到左压栈。thiscall不 是关键词,因此不能被程序员指定。 
5.naked   call  
采用1-4的 调用约定时,如果必要的话,进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄 存器,退出函数时则产生代码恢复这些寄存器的内容。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++中 更好的选择常常是函数
重载。

这篇关于C函数调用与入栈顺序的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/864190

相关文章

顺序表之创建,判满,插入,输出

文章目录 🍊自我介绍🍊创建一个空的顺序表,为结构体在堆区分配空间🍊插入数据🍊输出数据🍊判断顺序表是否满了,满了返回值1,否则返回0🍊main函数 你的点赞评论就是对博主最大的鼓励 当然喜欢的小伙伴可以:点赞+关注+评论+收藏(一键四连)哦~ 🍊自我介绍   Hello,大家好,我是小珑也要变强(也是小珑),我是易编程·终身成长社群的一名“创始团队·嘉宾”

web群集--nginx配置文件location匹配符的优先级顺序详解及验证

文章目录 前言优先级顺序优先级顺序(详解)1. 精确匹配(Exact Match)2. 正则表达式匹配(Regex Match)3. 前缀匹配(Prefix Match) 匹配规则的综合应用验证优先级 前言 location的作用 在 NGINX 中,location 指令用于定义如何处理特定的请求 URI。由于网站往往需要不同的处理方式来适应各种请求,NGINX 提供了多种匹

AutoGen Function Call 函数调用解析(一)

目录 一、AutoGen Function Call 1.1 register_for_llm 注册调用 1.2 register_for_execution 注册执行 1.3 三种注册方法 1.3.1 函数定义和注册分开 1.3.2 定义函数时注册 1.3.3  register_function 函数注册 二、实例 本文主要对 AutoGen Function Call

PHP7扩展开发之函数调用

前言 在这篇文章中我们将演示如何在扩展中调用函数,和调用对象的方法。代码示例如下: <?phpclass demo {public function get_site_name ($prefix) {return $prefix."信海龙的博客\n";}}function get_site_url ($prefix) {return $prefix."www.bo56.com\n";}

Win32函数调用约定(Calling Convention)

平常我们在C#中使用DllImportAttribute引入函数时,不指明函数调用约定(CallingConvention)这个参数,也可以正常调用。如FindWindow函数 [DllImport("user32.dll", EntryPoint="FindWindow", SetLastError = true)]public static extern IntPtr FindWindow

[数据结构]队列之顺序队列的类模板实现

队列是一种限定存取位置的线性表,允许插入的一端叫做队尾(rear),允许删除的一端叫做队首(front)。 队列具有FIFO的性质 队列的存储表示也有两种方式:基于数组的,基于列表的。基于数组的叫做顺序队列,基于列表的叫做链式队列。 一下是基于动态数组的顺序队列的模板类的实现。 顺序队列的抽象基类如下所示:只提供了接口和显式的默认构造函数和析构函数,在派生类中调用。 #i

[数据结构]栈之顺序栈的类模板实现

栈的数组实现形式,采用动态分配数组,不够时可以调整栈的大小。 Stack.h文件:主要定义栈的抽象基类,提供公共的接口函数。 #ifndef STACK#define STACK//栈的抽象基类template<class T>class Stack{public:Stack(){}~Stack(){}virtual void Push(const T& x)=0;virt

C++中类的构造函数调用顺序

当建立一个对象时,首先调用基类的构造函数,然后调用下一个派生类的 构造函数,依次类推,直至到达派生类次数最多的派生次数最多的类的构造函数为止。 简而言之,对象是由“底层向上”开始构造的。因为,构造函数一开始构造时,总是 要调用它的基类的构造函数,然后才开始执行其构造函数体,调用直接基类构造函数时, 如果无专门说明,就调用直接基类的默认构造函数。在对象析构时,其顺序正好相反。

七、Maven继承和聚合关系、及Maven的仓库及查找顺序

1.继承   2.聚合   3.Maven的仓库及查找顺序

线性表中顺序表的合并

对两个顺序表进行合并,算法的复杂度为O(La.size+Lb.size)。 已知: 顺序线性表La和Lb的元素按值非递减排列 归并La和Lb得到的顺序线性表Lc,Lc的元素也按值非递减排列。 代码定义: void mergeList(SeqList *La,SeqList *Lb,SeqList *Lc){Lc->capacity = La->size + Lb->size;Lc->b