[转] 栈的作用

.栈的整体作用

(1)保存现场/上下文

(2)传递参数:汇编代码调用c函数时,需传递参数

(3)保存临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量。

.为什么汇编代码调用c函数需要设置栈

之前看了很多关于uboot的分析,其中就有说要为C语言的运行,准备好栈。而自己在Ubootstart.S汇编代码中,关于系统初始化,也看到有栈指针初始化这个动作。但是,从来只是看到有人说系统初始化要初始化栈,即正确给栈指针sp赋值,但是却从来没有看到有人解释,为何要初始化栈。所以,接下来的内容,就是经过一定的探究,试图来解释一下,为何要初始化栈。
要明白这个问题,首先要了解栈的作用。关于栈的作用,要详细讲解的话,要很长的篇幅,所以此处只是做简略介绍。总的来说,栈的作用就是:保存现场/上下文,传递参数,保存临时变量

1.保存现场/上下文
现场/上下文,意思就相当于案发现场,总有一些现场的情况,要记录下来的,否则被别人破坏掉之后,你就无法恢复现场了。而此处说的现场,就是指CPU运行的时候,用到了一些寄存器,比如r0,r1等等,对于这些寄存器的值,如果你不保存而直接跳转到子函数中去执行,那么很可能就被其破坏了,因为其函数执行也要用到这些寄存器。因此,在函数调用之前,应该将这些寄存器等现场,暂时保持起来(入栈push),等调用函数执行完毕返回后(出栈pop),再恢复现场。这样CPU就可以正确的继续执行了。
保存寄存器的值,一般用的是push指令,将对应的某些寄存器的值,一个个放到栈中,把对应的值压入到栈里面,即所谓的压栈。然后待被调用的子函数执行完毕的时候,再调用pop,把栈中的一个个的值,赋值给对应的那些你刚开始压栈时用到的寄存器,把对应的值从栈中弹出去,即所谓的出栈。
其中保存的寄存器中,也包括lr的值(因为用bl指令进行跳转的话,那么之前的pc的值是存在lr中的),然后在子程序执行完毕的时候,再把栈中的lr的值pop出来,赋值给pc,这样就实现了子函数的正确的返回。

2.传递参数
C语言进行函数调用的时候,常常会传递给被调用的函数一些参数,对于这些C语言级别的参数,被编译器翻译成汇编语言的时候,就要找个地方存放一下,并且让被调用的函数能够访问,否则就没发实现传递参数了。对于找个地方放一下,分两种情况。一种情况是,本身传递的参数不多于4个,就可以通过寄存器传送参数。因为在前面的保存现场的动作中,已经保存好了对应的寄存器的值,那么此时,这些寄存器就是空闲的,可以供我们使用的了,那就可以放参数。另一种情况是,参数多于4个时,寄存器不够用,就得用栈了。

3.临时变量保存在栈中

包括函数的非静态局部变量以及编译器自动生成的其他临时变量。

4.举例分析C语言函数调用是如何使用栈的
对于上面的解释的栈的作用显得有些抽象,此处再用例子来简单说明一下,就容易明白了:用:arm-inux-objdump–d u-boot >dump_u-boot.txt可以得到dump_u-boot.txt文件。该文件就是中,包含了u-boot中的程序的可执行的汇编代码,其中我们可以看到C语言的函数的源代码,到底对应着那些汇编代码。
下面贴出两个函数的汇编代码,一个是clock_init,另一个是与clock_init在同一C源文件中的,另外一个函数CopyCode2Ram
33d0091c<CopyCode2Ram>:
33d0091c:  e92d4070  push   {r4, r5, r6, lr}
33d00920:  e1a06000  mov r6, r0
33d00924:  e1a05001  mov r5, r1
33d00928:  e1a04002  mov r4, r2
33d0092c:  ebffffef  bl  33d008f0 <bBootFrmNORFlash>
......
33d00984:  ebffff14  bl  33d005dc <nand_read_ll>
......
33d009a8:  e3a00000  mov r0, #0 ; 0x0
33d009ac:  e8bd8070  pop {r4, r5, r6, pc}
33d009b0<clock_init>:
33d009b0:  e3a02313  mov r2, #1275068416   ;0x4c000000
33d009b4:  e3a03005  mov r3, #5 ; 0x5
33d009b8:  e5823014  str r3,
......
33d009f8:  e1a0f00e  mov pc, lr
1clock_init部分的代码可以看到该函数第一行:33d009b0:  e3a02313  mov r2, #1275068416   ;0x4c000000就没有我们所期望的push指令,没有去将一些寄存器的值放到栈中。这是因为,我们clock_init这部分的内容,所用到的r2,r3等等寄存器,和前面调用clock_init之前所用到的寄存器r0,没有冲突,所以此处可以不用push去保存这类寄存器的值,不过有个寄存器要注意,那就是r14,即lr,其是在前面调用clock_init的时候,用的是bl指令,所以会自动把跳转时候的pc的值赋值给lr,所以也不需要push指令去将PC的值保存到栈中。而clock_init的代码的最后一行:33d009f8:e1a0f00e mov pc, lr就是我们常见的movpc,lr,把lr的值,即之前保存的函数调用时候的PC值,赋值给现在的PC,这样就实现了函数的正确的返回,即返回到了函数调用时候下一个指令的位置。这样CPU就可以继续执行原先函数内剩下那部分的代码了。
2CopyCode2Ram部分的代码其第一行:33d0091c:e92d4070 push {r4, r5, r6, lr}就是我们所期望的,用push指令,保存了r4,r5,r以及lr。用push去保存r4,r5,r6,那是因为所谓的保存现场,以后后续函数返回时候再恢复现场,而用push去保存lr,那是因为此函数里面,还有其他函数调用:33d0092c:  ebffffef  bl  33d008f0 <bBootFrmNORFlash>
......
33d00984:  ebffff14  bl  33d005dc <nand_read_ll>
......也用到了bl指令,会改变我们最开始进入clock_init时候的lr的值,所以我们要用push也暂时保存起来。而对应地,CopyCode2Ram的最后一行:33d009ac:e8bd8070 pop {r4, r5, r6,pc}就是把之前push的值,给pop出来,还给对应的寄存器,其中最后一个是将开始pushlr的值,pop出来给赋给PC,因为实现了函数的返回。另外,我们注意到,在CopyCode2Ram的倒数第二行是:33d009a8:e3a00000 mov r0, #0 ;0x0是把0赋值给r0寄存器,这个就是我们所谓返回值的传递,是通过r0寄存器的。此处的返回值是0,也对应着C语言的源码中的“return0”.
对于使用哪个寄存器来传递返回值:当然你也可以用其他暂时空闲没有用到的寄存器来传递返回值,但是这些处理方式,本身是根据ARMAPCS的寄存器的使用的约定而设计的,你最好不要随便改变使用方式,最好还是按照其约定的来处理,这样程序更加符合规范。

时间: 2024-08-26 23:07:59

[转] 栈的作用的相关文章

jvm虚拟机栈的作用

jvm虚拟机栈的作用 jvm虚拟机栈栈帧的组成 jvm虚拟机栈,也叫java栈,它由一个个的栈帧组成,而栈帖由以下几个部分组成 局部变量表-存储方法参数,内部使用的变量 操作数栈-在变量进行存储时,需要进行入栈和出栈 动态连接-引用类型的指针 方法出口-方法的返回 一段原程序代码 package com.lind.basic; public class Demo1 { static int hello() { int a = 1; int b = 2; int c = a + b; return

栈的作用

通过上节课的学习,我们简单了解了32位汇编和16位汇编的一些区别.并且我们把一段重复的代码,独立出来,单独调用. 需要用到这个功能的时候,用CALL指令跳转过去,执行完毕之后,用RET指令再跳回来.从而可以避免写大量重复的代码,软件开发效率自然也就上来了. 这节课,我们来写一个三个任意整数相加的代码或者函数还有其他的说法叫做过程,子程序,都是指一个意思. 如果我们需要10个数字,甚至100个数字要相加的话,如何设计这个函数呢?我们把所有的寄存器都用上,也不够用了. 很明显寄存器不够用了,还记得我

栈的整体作用

本文出至:http://www.cnblogs.com/xmphoenix/archive/2012/04/28/2475399.html 一.栈的整体作用 (1)保存现场/上下文 (2)传递参数:汇编代码调用c函数时,需传递参数 (3)保存临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量. 二.为什么汇编代码调用c函数需要设置栈 之前看了很多关于uboot的分析,其中就有说要为C语言的运行,准备好栈.而自己在Uboot的start.S汇编代码中,关于系统初始化,也看到有栈指针

栈回溯技术

转载于:http://blog.csdn.net/yangzhiloveyou/article/details/9042137 1.    前言 段错误.非法地址访问等问题导致程序崩溃的现象屡屡发生,如果能找到发生错误的函数,往往一眼就能看出BUG所在--对于这类比较简单的问题,比如使用空指针进行读写等,利用栈回溯技术可以很快定位.但是对于数组溢出.内存泄漏等问题导致的程序错误,往往隐藏很深,它们并不当场发作,即使我们一步一步跟踪到发生错误的语句时,也经常会让人觉得"这个地方根本不可能出错啊&q

数据结构基础之栈

数据结构之栈结构: 1.  栈的特点: 相比于一个普通的数组,栈控制了操作的方法,元素只能在栈顶入栈,也只能在栈顶出栈. 2.  栈的用途: 在深入优先搜索(DFS)中会用到,比如现在有一个图如下: 现在要从A出发要遍历整张图,那么首先看与A相连的B.D,任选一个,比如B,那么要先保存A,将其压入栈,然后遍历B,B又到C,然后C没有其余的路径就可以返回了,取出栈顶的元素B,B也没有其余路径,再取出栈顶元素A,现在A可以走D,那么D压入栈,走E,返回到D,再返回到A,现在A是出发点,又没有其余路径

基础算法复习——单调栈

单调栈: 单调栈非常容易理解,因为栈只能从一个方向进出. 单调栈的作用主要是可以找到一个数左边或右边第一个比它大或比它小的数. 下面以找出一个数左边第一个比它大的数为例. 举一列数:3,7,4,9,2,1 从左向右进行循环,(注意:入栈的是数组下标.在第一个元素入栈之前先把0入栈,在这里把a[0]的值设为无穷大) 对a[1],3比无穷大小,所以ans[1]=0,1入栈. 对a[2],7比3大,3出栈,所以ans[2]=0,2入栈 ...... 以此类推 Code: 1 for (int i=1;

Code Forces Gym 100971D Laying Cables(单调栈)

D - Laying Cables Time Limit:2000MS     Memory Limit:262144KB     64bit IO Format:%I64d & %I64u One-dimensional country has n cities, the i-th of which is located at the point xi and has population pi, and all xi, as well as all pi, are distinct. Whe

Struts2学习记录-Value Stack(值栈)和OGNL表达式

只是学习记录,把我知道的都说出来 一,值栈的作用 记录处理当前请求的action的数据. 二,小例子 有两个action:Action1和Action2 Action1有两个属性:name和passwd Action2有两个属性:name2和passwd2 请求从Action1进入,chain方法导向Action2 Struts.xml配置信息: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE st

大话数据结构之三(栈和队列)

栈的定义 栈是仅限有表尾进行插入和删除操作的线性表 允许插入和删除操作的一端称为栈顶,别一端称为栈底.不包含任何数据元素的栈称为空栈.栈又称为先进后出(Last In First Out)的线性表,简称为LIFO结构. 栈的插入操作叫做进栈,也称压栈.入栈. 栈的删除操作叫做出栈,也称弹栈 栈的抽象数据类型 栈的顺序存储结构 栈的插入和删除操作和时间复杂度均是O(1) 栈的不同状态下的示意图: 栈的实现源码如下: #include "stdio.h" #include "st