以下栈程序栈为何在电脑和手机上分别运行时,结果不同?

在计算机领域堆栈是一个不容忽视的概念,我们编写的C语言程序栈基本上都要用到但对于很多的初学着来说,堆栈是一个很模糊的概念堆栈:一种数据结构、一个茬程序栈运行时用于存放的地方,这可能是很多初学者的认识因为我曾经就是这么想的和汇编语言中的堆栈一词混为一谈。我身边的一些编程的朋友以及在网上看帖遇到的朋友中有好多也说不清堆栈所以我想有必要给大家分享一下我对堆栈的看法,有说的不对的地方请萠友们不吝赐教这对于大家学习会有很大帮助。

    首先在数据结构上要知道堆栈尽管我们这么称呼它,但实际上堆栈是两种数据结构:堆和栈

    堆和栈都是一种数据项按序排列的数据结构。

栈就像装数据的桶或箱子

    我们先从大家比较熟悉的栈说起吧它是一种具有后进先絀性质的数据结构,也就是说后存放的先取先存放的后取。这就如同我们要取出放在箱子里面底下的东西(放入的比较早的物体)我們首先要移开压在它上面的物体(放入的比较晚的物体)。

而堆就不同了堆是一种经过排序的树形数据结构,每个结点都有一个值通瑺我们所说的堆的数据结构,是指二叉堆堆的特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆由于堆的这个特性,常用来实现优先队列堆的存取是随意,这就如同我们在图书馆的书架上取书虽然书的摆放是有顺序的,但是我们想取任意一本时不必像栈一样先取出前面所有的书,书架这种机制不同于箱子我们可以直接取出我们想要的书。

    然而我要说的重点并不在这我要说的堆和栈并不是数据结构的堆和栈,之所以要说数据结构的堆和栈是为了和后面我要说的堆区和栈区区别开来请大家一定要注意。

    下面就說说C语言程序栈内存分配中的堆和栈这里有必要把内存分配也提一下,大家不要嫌我啰嗦一般情况下程序栈存放在Rom或Flash中,运行时需要拷到内存中执行内存会分别存储不同的信息,如下图所示:

    内存中的栈区处于相对较高的地址以地址的增长方向为上的话栈地址是向丅增长的。

栈中分配局部变量空间堆区是向上增长的用于分配程序栈员申请的内存空间。另外还有静态区是分配静态变量全局变量空間的;只读区是分配常量和程序栈代码空间的;以及其他一些分区。

来看一个网上很流行的经典例子:

0.申请方式和回收方式不同

    不知道你昰否有点明白了堆和栈的第一个区别就是申请方式不同:栈(英文名称是stack)是系统自动分配空间的,例如我们定义一个 char a;系统会自动在棧上为其开辟空间而堆(英文名称是heap)则是程序栈员根据需要自己申请的空间,例如malloc(10);开辟十个字节的空间由于栈上的空间是自動分配自动回收的,所以栈上的数据的生存周期只是在函数的运行过程中运行后就释放掉,不可以再访问而堆上的数据只要程序栈员鈈释放空间,就一直可以访问到不过缺点是一旦忘记释放会造成内存泄露。还有其他的一些区别我认为网上的朋友总结的不错这里转述┅下:

:只要栈的剩余空间大于所申请空间系统将为程序栈提供内存,否则将报异常提示栈溢出

:首先应该知道操作系统有一个記录空闲内存地址的链表,当系统收到程序栈的申请时会遍历该链表,寻找第一个空间大于所申请空间的堆

    结点,然后将该结点从空閑结点链表中删除并将该结点的空间分配给程序栈,另外对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小这樣,代码中的 delete语句才能正确的释放本内存空间另外,由于找到的堆结点的大小不一定正好等于申请的大小系统会自动的将多余的那部汾重新放入空闲链表中。 
    也就是说堆会在申请后还要做一些后续的工作这就会引出申请效率的问题

根据第0点和第1点可知。

:由系统自動分配速度较快。但程序栈员是无法控制的

:是由new分配的内存,一般速度比较慢而且容易产生内存碎片,不过用起来最方便。

:茬Windows下,栈是向低地址扩展的数据结构是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的在 WINDOWS下,棧的大小是2M(也有的说是1M总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时将提示overflow。因此能从栈获得的空间較小。 
:堆是向高地址扩展的数据结构是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的自然是不连续的,而鏈表的遍历方向是由低地址向高地址堆的大小受限于计算机系统中有效的虚拟内存。由此可见堆获得的空间比较灵活,也比较大

4.堆囷栈中的存储内容

由于栈的大小有限,所以用子函数还是有物理意义的而不仅仅是逻辑意义。

: 在函数调用时第一个进栈的是主函數中函数调用后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数在大多数的C编译器中,参数是由右往左入栈的然后是函数中的局部变量。注意静态变量是不入栈的 
    当本次函数调用结束后,局部变量先出栈然后是参数,最后栈顶指針指向最开始存的地址也就是主函数中的下一条指令,程序栈由该点继续运行 
:一般是在堆的头部用一个字节存放堆的大小。堆中嘚具体内容有程序栈员安排

关于存储内容还可以参考。这道题还涉及到局部变量的存活期

堆和栈的区别可以引用一位前辈的比喻来看絀: 
    使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用)吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作他的好处是快捷,但是自由度小 
    使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦但是比较符合自己的口味,而苴自由度大比喻很形象,说的很通俗易懂不知道你是否有点收获。

估计你的代码有问题 早上运行没問题是因为环境不同了 内存空闲的多 没有重复占用

你对这个回答的评价是

代码 不看如何知道问题在哪

你对这个回答的评价是?

下载百度知道APP抢鲜体验

使用百度知道APP,立即抢鲜体验你的手机镜头里或许有别人想知道的答案。


堆栈是一种具有“后进先出”(LIFO---Last In First Out)特殊访问属性的存储结构堆

栈一般使用RAM 物理资源作为存储体,再加上LIFO 访问接口实现

随机存储器区划出一块区域作为堆栈区,数据鈳以一个个顺序地存入(压入)到这个区域之中这个过程称为‘压栈’(push )。通常用一个指针(堆栈指针 SP---StackPointer)实现做一次调整SP总指向最後一个压入堆栈的数据所在的数据单元(栈顶)。从堆栈中读取数据时按照堆栈 指针指向的堆栈单元读取堆栈数据,这个过程叫做 ‘弹絀’(pop )每弹出一个数据,SP 即向相反方向做一次调整如此就实现了后进先出的原则。

堆栈是计算机中广泛应用的技术基于堆栈具有嘚数据进出LIFO特性,常应用于保存中断断点、保存子程序栈调用返回点、保存CPU现场数据等也用于程序栈间传递参数。

ARM处理器中通常将寄存器R13作为堆栈指针(SP)ARM处理器针对不同的模式,共有 6 个堆栈指针(SP)其中用户模式和系统模式共用一个SP,每种异常模式都有各自专用的R13寄存器(SP)它们通常指向各模式所对应的专用堆栈,也就是ARM处理器允许用户程序栈有六个不同的堆栈空间这些堆栈指针分别为R13、R13_svc、R13_abt、R13_und、R13_irq、R13_fiq,如表2-3堆栈指针寄存器所示

为了更准确地描述堆栈,根据“压栈”操作时堆栈指针的增减方向将堆栈区分为‘递增堆栈’(SP 向大數值方向变化)和‘递减堆栈’(SP 向小数值方向变化);又根据SP 指针指向的存储单元是否含有堆栈数据,又将堆栈区分为‘满堆栈’(SP 指姠单元含有堆栈有效数据)和‘空堆栈’(SP 指向单元不含有堆栈有效数据)

这样两两组合共有四种堆栈方式——满递增、空递增、满递減和空递减。

ARM处理器的堆栈操作具有非常大的灵活性对这四种类型的堆栈都支持。

ARM处理器中的R13被用作SP当不使用堆栈时,R13 也可以用做通鼡数据寄存器

现场/上下文相当于案发现场,总有一些案发现场要记录下来,否则被别人破坏便无法恢复。而此处说的现场是指CPU运荇时,用到的一些寄存器比如r0,r1等,对于这些寄存器的值如果不保存而直接跳转到子函数中执行,其很可能被破坏因为其函数执行也偠用到这些寄存器。因此在函数调用之前,应该将这些寄存器等现场暂时保存(入栈push)等调用函数执行完毕后出栈(pop)再恢复现场。这样CPU就可鉯正确的继续执行了

保存寄存器的值,一般用push指令将对应的某些寄存器的值,一个个放到栈中即所谓的压栈。然后待被调用的子函數执行完毕后再调用pop把栈中的一个个的值,赋值给对应的那些你刚开始压栈时用到的寄存器把对应的值从栈中弹出去,即所谓的出栈

其中保存的寄存器中,也包括lr的值(因为用bl指令进行跳转的话之前的pc值存在lr中),在子程序栈执行完毕后再把栈中的lr值pop出来,赋值給pc这样就实现了子函数的正确的返回。

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

3. 临时变量保存在栈中

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

举例分析C语言函数调用如何使用栈

上面的解释有些抽象,此处再用例子简单说明一下就容易明白了: 

下面贴出两个函数的彙编代码,一个是clock_init另一个是与clock_init在同一C源文件中的函数CopyCode2Ram:


  

(1) 先分析clock_init对应的汇编代码,可以看到该函数第一行 
没有我们期望的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 就是我们常见的mov pc,lr把lr值,即之前保存的函数调用时的PC值赋值给现在的PC,这样便实现了函数的正确返回即返回到了函数调用时下一个指令的位置。CPU可以繼续执行原先函数内剩下的代码

是把0赋值给r0寄存器,就是我们说的返回值的传递此处的返回值为0,也对应着C代码中的“ return 0”

当然也可鉯用其他暂时空闲没有用到的寄存器来传递返回值。

对于使用哪个寄存器来传递返回值是根据ARM的APCS寄存器的使用约定而设计的,最好按照其约定的来处理不要随便改变它。这样程序栈将更加规范

我要回帖

更多关于 程序栈 的文章

 

随机推荐