关闭

栈的作用

239人阅读 评论(0) 收藏 举报
分类:
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]保存寄存器的值


    
    



0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:7829次
    • 积分:307
    • 等级:
    • 排名:千里之外
    • 原创:23篇
    • 转载:5篇
    • 译文:0篇
    • 评论:2条
    文章分类
    最新评论