C语言中的函数调用,栈的使用
来源:互联网 发布:python cad二次开发 编辑:程序博客网 时间:2024/05/29 16:41
本文共包含一下四个部分。
- C源代码
- 注释
- 对应汇编代码:此汇编使用”gcc -S hello.c”命令编译生成,部分删减
- 栈空间的使用过程:包括5个部分,五张图
- C源码
int sayhello(int a,int b,int c){ int aa=100; int bb=200; bb=a; bb=b; bb=c; return aa;}main(){ int a=100; int b=10; int c=102; c=sayhello(a,b,c); c=200;}
- 汇编代码
080483ec <sayhello>: 80483ec: push %ebp //把main函数的ebp放入栈顶 80483ed: mov %esp,%ebp //ebp指向新的栈帧 80483ef: sub $0x10,%esp //为esp分配了16个字节空间 80483f2: movl $0x64,-0x8(%ebp) //栈内变量aa=200 80483f9: movl $0xc8,-0x4(%ebp) //栈内变量bb=200 8048400: mov 0x8(%ebp),%eax //ebp向上取出a=100 8048403: mov %eax,-0x4(%ebp) //赋值给bb 8048406: mov 0xc(%ebp),%eax //ebp向上取出b=10 8048409: mov %eax,-0x4(%ebp) //赋值给bb 804840c: mov 0x10(%ebp),%eax //ebp向上取值c=102 804840f: mov %eax,-0x4(%ebp) //赋值给bb 8048412: mov -0x8(%ebp),%eax //取出aa的值,存入eax 8048415: leave 8048416: ret //返回现场08048417 <main>: 804841a: sub $0x1c,%esp //为main函数的栈分配28字节空间 804841d: movl $0x64,-0xc(%ebp) //变量a=100 8048424: movl $0xa,-0x8(%ebp) //变量b=10 804842b: movl $0x66,-0x4(%ebp) //变量c=102 8048432: mov -0x4(%ebp),%eax //把参数c放入栈顶 8048435: mov %eax,0x8(%esp) 8048439: mov -0x8(%ebp),%eax //把参数b放入栈顶 804843c: mov %eax,0x4(%esp) 8048440: mov -0xc(%ebp),%eax //把参数a放入栈顶 8048443: mov %eax,(%esp) 8048446: call 80483ec <sayhello>//调用sayhello,隐含把EIP放入栈顶 804844b: mov %eax,-0x4(%ebp) //返回值赋给c 804844e: movl $0xc8,-0x4(%ebp) //c重新赋值 8048455: leave 8048456: ret
- 栈调用过程
1.
main函数汇编代码头部分我删去了几行,先不要理会,我们需要的是一个确定的干净的简单的起点,那就从三个变量的定义和初始化开始吧,从代码中可以看出,三个变量的定义和创建使用了ebp的相对位置并且是逆序存放;
此时esp指向的位置比较有意思,从ebp到esp这段空间就是gcc为main函数分配的栈帧,也就是说main函数的运行以后只会使用这一段空间了;这一段空间的大小由两部分组成,如图解释。main函数中定义了三个局部变量12个字节,gcc为局部变量分配了16个字节;main函数调用的函数最多入参个数是3,gcc再分配12个字节,所以main函数的栈帧总共有28个字节,7个字。
2.
main函数准备调用sayhello了!
1、先参考esp的位置把入参mov栈中,没有使用PUSH,没有使用PUSH,没有使用PUSH
2、调用call指令,call指令其实隐含了一个PUSH指令,一个MOV指令。他先把当前main的EIP压入栈中,然后把sayhello的代码位置MOV给EIP。程序下一步执行就到了sayhello位置了。注意,保护现场工作尚未完成!
3.
进入sayhello函数后继续进行现场保护工作,保存main函数的ebp。之后ebp可以指向为sayhello分配的栈帧了,再为sayhello分配局部变量空间16个字节,创建两个变量。接下来就要使用入参了,入参的访问使用了当前ebp的相对寻址,不过不是向sayhello的栈帧,而是向上取main函数栈帧底部的三个参数!ok,更新完毕,准备返回。
4.
准备返回:1、返回的参数放入eax寄存器。2、执行ret。ret负责恢复现场,和call一样隐含了好几个POP和MOV。先让esp指向当前栈帧的底部ebp,然后pop出ebp回复main的栈帧,pop出EIP回复main的代码指针,此时虽然esp上面还存着sayhello的入参,但已经没用了。
5.
函数继续运行,EIP已经指向main的代码了,main开始对c进行赋值,先把eax里的数据拿出来,然后赋值给c。结束。
注释
- esp:当前栈的栈顶指针
- ebp:当前函数的栈帧基地址
- ess:栈空间的基地址
- eax:临时变量寄存器
- EIP:当前函数的代码指针
栈有很多栈帧组成,随着程序的运行,每进入新的函数都需要创建一个栈帧,函数只使用自己的栈帧,函数运行结束够要弹出栈帧。
刚进入函数时,esp会先跳一段空间,用来存储局部变量。
可以看出,栈只有在保护现场时使用push,pop,其他都是采用寻址的方式使用的。
- C语言中的函数调用,栈的使用
- 使用OC调用C语言的函数
- C语言结构体指针在函数调用中的使用
- c语言函数调用栈的变化
- c++和c语言中的函数相互调用的问题
- C语言函数的调用
- 简析C语言中的函数调用栈机制
- 浅谈C语言中的函数调用方式-----栈帧结构
- C语言调用Python脚本中的函数
- C语言中的函数调用(栈帧)
- C语言中的函数调用(栈帧)
- 使用 lua 调用 c 语言写的函数
- C语言函数调用栈
- 相对路径的使用&&调用.C中的函数
- 【精华】C语言中的scanf函数的使用
- C语言中的函数调用和函数返回值
- C语言函数调用模型[调用过程中在堆栈中的出栈、进栈顺序]
- c语言中函数的调用的栈帧
- Android快速SDK(4)对话框AlertDialog
- git删除远程文件夹或文件的方法
- Qt编译安装OpenCV3.0
- eclipse快捷键
- JavaScript获取当前项目各种路径
- C语言中的函数调用,栈的使用
- [leetcode-302]Smallest Rectangle Enclosing Black Pixels
- Spark实战1:单节点本地模式搭建Spark运行环境
- oracle修改字符集----转载自Linux公社
- JasperReport学习笔记2-创建简单的报表例子
- JDBC连接各数据库
- jquery+json+struts2+mybatis实现的多级关联下拉效果
- “一大”代表包惠僧de离奇经历
- JDBC PreparedStatement & Statement