栈帧及可变参数列表
来源:互联网 发布:oracle数据库中文注释 编辑:程序博客网 时间:2024/06/07 16:20
一. 栈帧的概念
栈帧也叫作过程活动记录,是一个函数的执行环境,在栈帧中记录着函数的变量和返回地址。
每个栈帧对应一个未完成的函数,每个函数都有其相对应的栈帧。在调用被调函数时,栈帧被创立;被调函数结束后,进行压栈,
释放临时变量,返回至被调函数的下一步指令的地址处。
在栈帧中,地址由高向低延伸。栈帧的最高地址为栈底,最低地址为栈顶。
二.使用栈帧的情况
在函数调用中,有很多情况都可以用栈帧的原理进行解释。
1. 虚实结合(函数的实参传给形参)
2. 局部变量的释放
3. 被调函数结束后的返回地址
4. return C 的值的带回
三. 分析栈帧的创建过程
首先,了解关于栈帧的几个名词
1. ebp—栈底 指针寄存器。其内存放一个指针,该指针指向栈帧的底部。
2. esp—栈顶 指针寄存器。其内存放一个指针,该指针指向栈帧的顶部。
3. PC指针 : 永远指向当前运行指令的下一条指令。
用下面这个简单程序分析栈帧的使用情况
#include<stdio.h>
int fun(int x,int y)
{
int c = 20;
return c;
}
int main()
{
int a = 1;
int b = 2;
int ret = fun (a , b);
return 0;
}
栈帧的创建和使用分为三个阶段
第一阶段:主函数为调用者函数,进入调用者函数,在系统栈中为调用者函数分配栈帧。
(1) 在调用者函数的栈帧中为变量 a,b 分配相应的存储空间
(2) 程序执行至 int fun(int x,int y) 时,进行实参与形参的虚实结合,形成临时拷贝并将其压入栈中。值的注意的是,
实参与形参的虚实结合的顺序是从右向左,先进行参数 b 的临时拷贝,再进行 a 的临时拷贝。
(3) 形参实例化之后,执行 call 指令。
call 指令有两个重要的作用:① 执行 jmp 指令,跳转至被调函数的入口地址,执行被调函数的相关操作
② 保存被调函数的下一条指令的地址,便于被调函数结束后的返回
以上步骤运行结束之后,调用者函数的栈帧创建结束,下面开始进入被调函数的栈帧的创建。
第二阶段:在主函数中运行到被调函数时,为被调函数分配栈帧。
(1) push ebp ; // 保存调用者函数的栈底,放在被调函数的栈底,便于返回
被调函数结束后的返回仅靠 call 指令中的记录下一指令的地址是无法实现的,需要将 call 指令和 push 指令结合
起来,才能使被调函数结束后能返回到正确的位置。
(2) move ebp ,esp; // 将调用者函数的栈顶赋值给被调函数的栈底
此时,被调函数的栈底就是调用者函数的栈顶。
(3) sub esp ,occh; // 给被调函数的栈顶减去一个随机值,指针下移,为被调函数的栈帧创造空间。
第三阶段:被调函数结束后,压栈,释放局部变量,带回返回值,返回到主函数运行指令的下一步指令。
(1) move eax ,dword ptr [c] ; // 将返回值 c 的值保存在寄存器 eax 中
(2) move esp ,ebp; // 将栈底的地址赋给栈顶的地址。栈顶移动到栈底的位置,释放局部变量。
(3) pop ebp; // 将被调函数栈底保存的调用者函数的栈底地址弹出
(4) ret ; // 返回地址出栈,返回至主函数的下一指令处。
(5) sub esp , occh; // 被调函数结束后,局部变量被释放,使栈顶指针上移,恢复到未形参实例化之前的位置。
(6) move dword ptr [ret] ,eax ; // 将寄存器中存储的返回值 c 返回至主函数中的变量 ret 中。
以上就是整个函数调用的栈帧使用原理。
四. 可变参数列表
在普通函数中,形参的个数是固定的,调用函数时,通过实参与形参的虚实结合实现函数的调用。
普通函数存在的问题: ① 不对参数个数进行检测
② 实参与形参不一定一一对应
为了使函数能够在不同的情况下接收不同数目的参数,我们使用可变参数列表。
可变参数列表的一般形式:
(1) # include < stdarg . h >
使用可变参数列表时,需要引入的头文件,以下的几个宏都包含在这个头文件中。
(2) va_list 变量名 ;
例 va_list args ;
这个变量是一个存储可变参数列表的地址的指针,通过这个指针变量找到参数列表的地址,再结合参数类型,得到参数值。
(3) va_start ( 指针变量名 , 可变参数列表前的最后一个参数 )
例 va_start ( args ,start );
将最后一个固定参数的地址传送给指针变量 args ,实现对 va_list 类型变量的初始化。通过指针下移访问下面的参数。
(4) va_arg ( 指针变量名 , 下一个参数的类型 );
例 va_arg ( args , type );
通过这个宏返回至参数列表的首地址,结合参数类型,得到参数的值。
(5) va_end ( 指针变量名 );
例 va_end (args );
由于被调函数在调用时不知道参数的正确数目,所以需要人为的加上一个结束条件使得函数结束。
可变参数列表的限制:
① 函数不能确定参数的个数
② 这些宏不能确定下一个参数的类型,在宏 va_arg 中需谨慎判断参数的类型
③ 在可变参数列表中必须存在一个命名参数,否则无法实现 va_start 对 va_list 的初始化
④ 使用可变参数列表对参数的访问必须从第一个参数开始,可以中途停止但不能从中间的某一个参数开始访问。
使用可变参数列表模拟实现 printf 函数
# include < stdio.h >
# include < stdarg.h >
void printf ( char *format, ...)
{
va_list arg;
va_start(arg, format);
while(*format != '\0')
{
switch(*format)
{
case 's':
{
char *ret = va_arg(arg, char*);
while(*ret)
{
putchar(*ret);
ret++;
}
}
break;
case 'c':{
putchar(va_arg(arg,char));
break;
default:
putchar(*format);
break;
}
format++;
}
va_end(arg);
}
int main()
{
print("sccc\n","he",'l','l','o');
return 0;}
五. 大端小端的简单理解
大端小端又称大尾小尾,顾名思义得
大端(大尾): 和我们的思维方式相一致,数据的高字节保存在低地址中,数据的低字节保存在高地址中。
小段(小尾): 小端模式与大端模式相反,数据的低字节保存在低地址中,数据的高字节保存在高地址中。
- 栈帧及可变参数列表
- main函数参数及可变参数列表
- 可变参数列表原理及应用
- 可变参数列表用法及原理
- 浅析可变参数列表及实例分析
- Java可变参数列表
- 可变参数列表
- 关于可变参数列表
- 可变参数列表
- c++ 可变参数列表
- 可变参数列表
- cstdarg可变参数列表
- 可变参数列表
- java可变参数列表
- 可变参数列表
- Java 可变参数列表
- 可变参数列表
- C可变参数列表
- C++浅拷贝与深拷贝(程序员面试宝典试题)
- Hadoop1.x安装:完全分布式安装
- JSP-JSTL-import、redirect、url处理、常用函数
- win32的创建窗口代码
- HDU 2018 母牛的故事(DP递推)
- 栈帧及可变参数列表
- Python 学习->四类逻辑运算符
- C++ 模拟String类增删查改
- Java中创建对象的5种方式
- NYOJ 153 king VS king
- Linux环境下所赋予的权限详细内容
- 空指针异常理解
- Ajax ------on the way
- 抽象代数学习笔记(7)对称群与置换群