栈的作用

原创 2016年06月01日 11:23:16
1 概念
栈是一种具有后进先出性质 的数据组织方式,也就是说后存放的先取出,先存放的后取出。栈底是第一个进栈 
的数据所处的位置,栈顶是最后一个进栈的数据所处的位置。


2 分类
根据SP指针指向的位置,栈可以分为满栈和空栈。 
1. 满栈:当堆栈指针SP总是 指向最后压入堆栈的数据 
2. 空栈:当堆栈指针SP总是 指向下一个将要放入数据空位置 
ARM采用满栈!
根据SP指针移动的方向,栈 可以分为升栈和降栈。 
1. 升栈:随着数据的入栈, SP指针从低地址->高地址 移动 
2. 降栈:随着数据的入栈, SP指针从高地址->低地址 移动 
ARM采用降栈!
  


3 栈桢
简单的讲,栈帧(stack frame) 就是一个函数所使用的那部分栈,所有函数的栈帧串起来就组成了一个完整的栈。
栈帧的两个边界分别由fp(r11)和sp(r13)来限定。

3 代码分析
    通过一个简单的程序分析栈的作用与栈的操作。
    代码清单:

 1 int func_a(int a0, int a1, int a2, int a3, int a4, int a5)
  2 {
  3
  4 return a5;
  5 }
  6
  7
  8 int main(void)
  9 {
 10 int a, b;
 11
 12 a = 1;
 13 b = 2;
 14 b = func_a(0, 1, 2, 3, 4, 5);
 15
 16 return 0;
 17 }

    通过编译、反汇编得到相应的汇编代码,真相就在汇编代码。
    编译:arm-linux-gcc -g test.c -o test
    反汇编:arm-linux-objdump -D -S test > test_dump
    查看test_dump: vi test_dump

184 00008380 <func_a>:
185 int func_a(int a0, int a1, int a2, int a3, int a4, int a5)
186 {
187 8380: e52db004 push {fp} ; (str fp, [sp, #-4]!)
188 8384: e28db000 add fp, sp, #0
189 8388: e24dd014 sub sp, sp, #20
190 838c: e50b0008 str r0, [fp, #-8]
191 8390: e50b100c str r1, [fp, #-12]
192 8394: e50b2010 str r2, [fp, #-16]
193 8398: e50b3014 str r3, [fp, #-20]
194
195 return a5;
196 839c: e59b3008 ldr r3, [fp, #8]
197 }
198 83a0: e1a00003 mov r0, r3
199 83a4: e28bd000 add sp, fp, #0
200 83a8: e8bd0800 pop {fp}
201 83ac: e12fff1e bx lr
202
203 000083b0 <main>:
204
205
206 int main(void)
207 {
208 83b0: e92d4800 push {fp, lr}
209 83b4: e28db004 add fp, sp, #4
210 83b8: e24dd010 sub sp, sp, #16
211 int a, b;
212
213 a = 1;
214 83bc: e3a03001 mov r3, #1
215 83c0: e50b300c str r3, [fp, #-12]
216 b = 2;
217 83c4: e3a03002 mov r3, #2
218 83c8: e50b3008 str r3, [fp, #-8]
219 b = func_a(0, 1, 2, 3, 4, 5);
220 83cc: e3a03004 mov r3, #4
221 83d0: e58d3000 str r3, [sp]
222 83d4: e3a03005 mov r3, #5
223 83d8: e58d3004 str r3, [sp, #4]
224 83dc: e3a00000 mov r0, #0
225 83e0: e3a01001 mov r1, #1
226 83e4: e3a02002 mov r2, #2
227 83e8: e3a03003 mov r3, #3
228 83ec: ebffffe3 bl 8380 <func_a>
229 83f0: e1a03000 mov r3, r0
230 83f4: e50b3008 str r3, [fp, #-8]
231
232 return 0;
233 83f8: e3a03000 mov r3, #0
234 }
235 83fc: e1a00003 mov r0, r3
236 8400: e24bd004 sub sp, fp, #4
237 8404: e8bd8800 pop {fp, pc}


3.1 函数内部分析
    [1] main函数
    第208条汇编指令 push {fp, lr}; 即str fp [sp, #-4]!; str lr [sp, #-4]!;
    作用:
    (1)将fp中的值赋给地址sp-4;
    (2)将lr中的值赋给地址sp-8;
    (3)将sp-8保存到sp中,“!”表示同时改变sp本身的值;
    
    第209条指令 add fp, sp, #4
    作用:
    (1)将sp-4赋值给fp,作为main新栈的上边界;

    第210条指令 sub sp, sp, #16
    作用:
    (1)将sp的值减16,作为main新栈的下边界,至此main新栈已开辟完成;

    a = 1; 
    对应的汇编指令为:
    mov r3, #1; 
    str r3, [fp, #-12]; 地址 fp-12存储的就是a的值,且值为1
    对b的赋值也是如此。说明若一个变量被声明了,但没有使用,则不会在栈中分配内存。
    如果上诉代码中的b = 2;注释掉,则汇编代码中不会有str r3, [fp, #-8],就是说没有分配栈的内存。

     b = func_a(0, 1, 2, 3, 4, 5);

后2个参数保存在栈
mov r3, #4
str r3, [sp]
mov r3, #5
str r3, [sp, #4]

前4个保存在寄存器
mov r0, #0
mov r1, #1
mov r2, #2
mov r3, #3

跳转到func_a
bl 8380 <func_a>

将返回值存储在r3
mov r3, r0

将返回值返回给局部变量
str r3, [fp, #-8]
        
    func_a的形参达到6个,从左往右的后2个参数被保存到栈中,前四个直接保存在CPU的寄存器。
    
        mov r0, r3; r3存放的是main的返回值
        add sp, fp, #0; 将fp的值返回给sp,fp存储的是原先的sp的值减4
        pop {fp}; 弹出fp,并且sp的值加4,fp和sp均恢复到调用main函数之前的状态
        bx lr; 跳转到原先的指令处    
    待main函数执行完了,则对栈进行回收处理。

    [2]func_1函数

push {fp} ; (str fp, [sp, #-4]!); 将寄存器fp中的值存储在地址sp-4中
add fp, sp, #0; fp中保存sp的值
sub sp, sp, #20; 确定func_a的栈的下边界
将寄存器的形参存储在栈中,寄存器只起一个中转的作用
str r0, [fp, #-8]; 
str r1, [fp, #-12];
str r2, [fp, #-16];
str r3, [fp, #-20];

return a5;
将a5的值存储在main的栈中,即将a5的值返回给main中的局部变量
ldr r3, [fp, #8];
    
    [3]func_a 函数执行完成后
mov r0, r3; 将返回值转存在r0中,r0座位默认的函数返回寄存器
add sp, fp, #0; 将fp的值返回给sp,fp存储的是原先的sp的值减4
pop {fp}; 弹出fp,并且sp的值加4,fp和sp均恢复到调用main函数之前的状态
bx lr; 跳转到原先的指令,lr存储的是返回的地址

4 作用

    总的来说,栈的作用为如下所示:
    [1]保存局部变量
    [2]传递函数的参数
    [3]保存寄存器的值


    
    



通俗解析IRP和I/O设备栈在内核程序中的作用

正文: 言归正传,所有的I/O请求都是以IRP(I/O请求包)的形式来提交的,同时内核程序的所有分发函数(Dispatch Function)的第二个参数都是 PIRP(也即是指向IRP的指针...
  • md521
  • md521
  • 2015年01月13日 21:57
  • 837

执行上下文(栈)/作用域(链)/with

执行上下文(栈) 每一次代码执行和函数调用都会产生一个执行环境,称为执行上下文。 一个执行上下文(caller)又可以激活(调用)另一个执行上下文(callee),这时caller会暂停自身的执行把控...
  • ymjring
  • ymjring
  • 2014年12月08日 17:09
  • 1409

[转载] C程序的函数栈作用机理

原文:http://bluedream.me/post/ji-zhu/function_stack 一段错误程序引发的思考 自从开始研究web应用以后,已经很少接触系统底层的程序了。昨天一同学...

ia32中程序调用返回时call.ret.leave的作用和栈变换的说明

ia32中程序调用和返回时所用到的call、ret、leave三个指令的作用和程序栈变换的说明(包括esp,ebp,eip)...

通俗解析IRP和I/O设备栈在内核程序中的作用(转自看雪)

言归正传,所有的I/O请求都是以IRP(I/O请求包)的形式来提交的,同时内核程序的所有分发函数(Dispatch Function)的第二个参数都是 PIRP(也即是指向IRP的指针)。为了说...
  • wzsy
  • wzsy
  • 2017年03月14日 16:29
  • 165

前端基础进阶(六):在chrome开发者工具中观察函数调用栈、作用域链与闭包

配图与本文无关 在前端开发中,有一个非常重要的技能,叫做断点调试。 在chrome的开发者工具中,通过断点调试,我们能够非常方便的一步一步的观察JavaScript的执行过程,直观感知...
  • Fuohua
  • Fuohua
  • 2017年04月27日 11:45
  • 232

对象访问,java栈,java堆,hashcode()的作用

1.对象访问 Java虚拟机学习 - 对象访问

既然堆已经可以管理变量的生命周期,那么栈的作用个是什么?

有了栈为什么还要堆?

struts2异常处理机制-----值栈作用

 一、处理一般异常(javaBean异常)     struts2进行异常处理首先需要添加exception拦截器,而默认拦截器栈已经加入了这个拦截器,所以不用特意的声明。在Struts2...
  • YMR0717
  • YMR0717
  • 2016年03月23日 11:31
  • 378

C语言函数调用时参数压栈的顺序以及函数指针的作用

1、函数参数压栈的顺序 很多人都知道压栈的顺序时从右向左进行压栈,具体的可观测的结果如下程序运行。我们都知道Pascal的参数入栈顺序时自左向右的,但是为什么C语言会选择自右向左呢?这也是C语言比pa...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:栈的作用
举报原因:
原因补充:

(最多只允许输入30个字)