范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

深入研究栈

  1.栈是什么
  在计算机软件开发过程中,我们经常听到,看到,用到"栈"。 那么到底"栈"是什么呢?"栈"的作用是什么呢?
  不妨我们先看一下《新华字典》是如何定义的。
  栈基本含义是储存货物或供旅客住宿的房屋,如货栈,客栈。
  说到客栈又让我联想到一部著名的武侠电影"新龙门客栈",客栈就是提供给各位大侠一个临时居住的房间。
  既然客栈也是"栈",说明栈的底层的含义一样,都是用来做存储的。在计算机中"栈"是数据存储空间中的一个区域, 用于储存特定的数据 。
  栈的承载实体通常是随机存取存储器(RAM),CPU可以直接与RAM交换数据,RAM在工作状态下,可以随时从任何一个指定的地址写入(存入)或读出(取出)信息。
  综上所述,在计算机中"栈"就是存储数据的一个存储区域 。通常说的 "堆栈"和"栈"  ,这两者意义相同。 2.栈的分类
  我们知道栈就是用来存储数据的,在实际使用中栈并不是只有唯一的一种形式,而是分为多种类型,接下来我们了解一下栈的分类。
  根据栈在存储器中的增长方向,可以把栈分为 递减栈 和 递增栈  :
  递减栈(Descend)  : 向栈写入数据时,栈的生长方向是高地址到低地址。
  递增栈(Increase)  : 向栈写入数据时,栈的生长方向是低地址到高地址。
  根据栈指针SP指向的位置,可以把栈分为 满堆栈 和 空堆栈 :
  满堆栈(Full Stack) : SP指针始终指向栈顶元素,向栈写入数据时先移动SP指针,再将数据放入SP指向的地址。
  空堆栈(Empty Stack) : SP指针始终指向下一个将要放入元素的位置,向栈写入数据时先将数据放入SP指向的地址,再移动SP指针。
  根据栈的增长方向和栈指针的位置,栈可以分为以下 4种基本类型 :
  满增栈(FA) : 栈指针指向最后压入的数据,栈的生长方向是低地址向高地址。
  满减栈(FD) : 栈指针指向最后压入的数据,栈的生长方向是高地址向低地址。
  空增栈(EA) : 栈指针指向下一个将要压入数据的地址,栈的生长方向是低地址向高地址生长。
  空减栈(ED) : 栈指针指向下一个将要压入数据的地址,栈的生长方向是高地址向低地址生长。
  其中满减栈是使用得最多的一种栈类型。 3.栈的操作
  计算机的结构框图如下:
  由图可知在处理器中 直接参入运算的是寄存器堆中的寄存器 ,逻辑运算的结果可以输出到数据存储器的地址端口和寄存器堆的数据端口。
  寄存器堆中的寄存器值可以输出到数据存储器的数据端口,实现暂存寄存器的值。
  栈操作就是将寄存器的值存入数据存储器,或是将数据存储器中的数据回读到寄存器中。
  重要的事情说三遍:
  栈就是用来暂存处理器中寄存器的值!
  栈就是用来暂存处理器中寄存器的值!
  栈就是用来暂存处理器中寄存器的值!
  ARM构架处理器中的寄存器组如下:
  栈操作的两个指令: 入栈PUSH和出栈POP 。
  对于 PUSH操作 ,处理器先减小SP值,然后将指定寄存器存储到SP寄存器指向的存储器地址。
  对于 POP操作 ,处理器先将SP指向的存储器地址存储到指定寄存器中,然后将SP寄存器值增加。
  栈的操作具有" 先进后出 "的特性,先存入栈的数据,在栈的底部,后存入栈的数据在顶部,栈中的数据只能从栈顶部读出,因此就有了"先进后出"。
  以满减栈为例 ,下图展示了 连续两次PUSH操作 ,SP寄存器和数据存储器中的数据变化。
  PUSH操作时处理器 先减小SP值 , 然后将指定寄存器存储到SP寄存器指向的存储器地址 。
  注意:执行PUSH操作后数据存储器中(栈空间)的数据发生变化,但是指定寄存器的值还是原有的值(不会被清零)。
  以满减栈为例 ,下图展示了 连续两次POP操作 ,SP寄存器和指定寄存器的数据变化。
  POP操作时处理器 先将SP指向的存储器地址存储到指定寄存器中 ,然后 将SP寄存器值增加 。
  注意:执行POP操作后指定寄存器的数据发生变化,但是数据存储器中(栈空间)的数值还是原有的值(不会被清零)。
  PUSH操作后由于指定的寄存器的数据被保存,因此此后可以将该寄存器用于其它用途,当该寄存器完成其它操作后可以通过POP操作恢复原先数值 。
  由于在处理器中只有寄存器能直接参入运算,通常情况下寄存器的数量有限(通常为16个或者32个),栈操作相当于将寄存器的 数量进行扩展 。这种操作类似火影忍者中鸣人的影分身(一个真身多个假身)。
  总结栈操作:
  1、栈是一个数据存储空间(栈空间)。
  2、栈有一个栈指针,指向当前栈地址。
  3、每次执行PUSH操作后栈空间的数据发生变化,每次执行POP操作后指定寄存器的数据发生变化。
  4、每次PUSH和POP操作后SP栈指针都会自动调整(无需用户介入)。 4.栈的作用
  上文描述了栈空间本质上暂存处理器中寄存器的运算结果,具体来说 栈用于如下4情况的数据存储 :
  1、用于保存函数执行前的寄存器的值,以便函数结束时恢复。
  2、用于存储局部变量。
  3、用于传递函数调用时的参数。
  4、用于存储中断产生时的状态寄存器和通用寄存器的数值。 4.1函数调用前保存寄存器值
  当被调用的函数需要使用寄存器进行数据处理时,需要使用栈临时保存寄存器的数值,当函数结束时恢复寄存器的数值。
  测试代码如下: /****************************************** * @作者     : liwei    * @Github 	: liyinuoman2017 *******************************************/  void test(void) {	 	int l , m , n; 	l = 9; 	m = 8; 	n = l+ m; }  void stack_test(void) {	 	int i , j , k; 	i = 1; 	j = 2; 	test(); 	k = i+ j; }  int main(void) { 	int a0,a1,a2,a3,a4,a5,a6,a7,a8,a9; 	a0 = 1; 	a1 = 3; 	a2 = 1; 	a3 = 4; 	a4 = 1; 	a5 = 7; 	a6 = 9; 	a7 = 5; 	a8 = 2; 	a9 = 0; 	/***调用测试函数**/ 	stack_test(); 	 	a0 = a1 + a2; 	a3 = a4 + a5; }
  mian函数中定义了较多变量从而占用了大量寄存器,stack_test函数定义了3个变量,在调用stack_test函数前处理器的 寄存器已经被全部占用 ,为了提供寄存器给stack_test函数使用,就必须 先将部分寄存器保存到栈中 ,当stack_test函数执行结束后从栈中恢复部分寄存器的值。
  main函数 反汇编  后的结果如下:
  stack_test函数 反汇编  后的结果如下:
  根据stack_test函数汇编代码可知:由于stack_test函数 需要使用R4,R5,R6而这3个寄存器 ,由于这3个 寄存器已经被占用 ,因此stack_test函数在使用R4,R5,R6前,必须 先将这3个寄存器保存到栈中 ,并在stack_test函数执行结束返回前 从栈中恢复R4,R5,R6这3个寄存器的数值 。
  总结:调用子函数时,子函数内需要使用寄存器,由于寄存器已经被占用,因此需要将函数调用者使用的寄存器保存到栈中,待调用函数结束后再恢复寄存器。 4.2存储局部变量
  局部变量可以直接存储到寄存器中,但是当局部变量比较多时,一部分变量将会保存到栈中。
  测试代码如下: /****************************************** * @作者     : liwei    * @Github 	: liyinuoman2017 *******************************************/  int main(void) { 	int a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15; 	a0 = 1; 	a1 = 3; 	a2 = 1; 	a3 = 4; 	a4 = 1; 	a5 = 7; 	a6 = 9; 	a7 = 5; 	a8 = 2; 	a9 = 1; 	a10 = 3; 	a11 = 1; 	a12 = 4;	 	a13 = 1; 	a14 = 7; 	a15 = 9; 	 	a0 = a1 + a2; 	a3 = a4 + a5; 	a6 = a7 + a8; 	a9 = a10 + a11; 	a12 = a13 + a14; 	a15 = a1 + a2; 	 }
  main函数反汇编后的结果如下:
  根据反汇编可知,当使用的局部变量较多时, 部分局部变量会被保存到栈中 。 4.3保存子函数参数
  ARM体系结构的过程调用标准AAPCS (Procedure Call Standard for the ARM Architecture),它规定了子程序的调用规则,其中ARM寄存器在子程序调用中的参数传递规则如下:
  当子程序参数不超过4个时,使用寄存器R0-R3来传递参数, 当参数超过4个时,超出的参数使用栈来传递参数 。
  调用有 2个参数的子函数 的测试代码如下: /****************************************** * @作者     : liwei    * @Github 	: liyinuoman2017 *******************************************/ int sum(int a , int b) { 	return ( a + b ); }  int main(void) { 	int a0 = 0;  	a0 = sum( 3 , 4 ); 	 	while(1); }
  代码反汇编如下:
  由反汇编可知,在跳转到sum函数前使用 R0,R1保存了sum函数的2个参数 。
  调用有 6个参数的子函数 的测试代码如下: /****************************************** * @作者     : liwei    * @Github 	: liyinuoman2017 *******************************************/ int sum(int a , int b, int c, int d, int e, int f) { 	return ( a + b  + c  + d  + e  + f ); }  int main(void) { 	int a0 = 0;  	a0 = sum( 1 , 3 , 2 , 4 , 7 , 9 ); 	 	while(1); }
  代码反汇编如下  :
  由反汇编可知,在跳转到sum函数前使用R0,R1,R2,R3保存了后4个函数参数, 同时将第一个和第二个参数存入栈中  ,在sum函数中从栈中加载参数带入计算。
  因此在很多编程规范规定 函数参数不能超过3个  。原因是利用栈传递参数影响程序执行效率。 4.4中断时存储寄存器
  当处理器产生异常或中断时,在进入异常函数前,处理器硬件会自动保存部分寄存器,待异常函数执行完毕后,执行异常返回时,处理器硬件会自动恢复之前保存的寄存器的值。
  ARM构架处理器中断时序图如下:
  5.栈区的优势
  计算机中的内存分区通常如下:
  数据存储器通常分为: 栈区,堆区,静态区 。
  栈区用于存储特定用法的变量, 栈区内的变量是临时的变化的,栈区使用大小是动态变化的 。
  堆区用于用户主动分配存储数据。
  静态区用于存储静态变量,静态区内的变量的使用周期是整个程序运行周期,每个变量对应一个地址,"一个萝卜一个坑"。
  堆区在本文不进行描述,本节重点对比栈区和静态区。 栈区的优势是什么?栈区能用静态区代替吗?  5.1栈区的优势
  假设现在有如下一个函数: void test(void) { 	char buff[100]; 	... 	 }
  函数test中定义了一个100字节的数组buff,因此运行程序执行到test函数时会临时占用100字节的栈区。假设把buff该成静态类型static char buff[100] ,此时程序会使用100字节静态区一直保存数组buff。
  感觉好像没啥优势呢, 静态区和栈区都会消耗100字节 。
  如果现在有50个类似test的函数,每个函数中需要使用100字节,如果全部使用静态变量,程序将使用 5000字节 静态区。
  如果函数不使用静态变量,而是使用临时变量,此时程序将临时占用 最少100字节 (50个函数不发生嵌套调用), 最大5000字节 (50个函数全部嵌套),事实上很难出现50个函数全部嵌套的情况,按照嵌套10层算只需要1000字节栈区即可满足程序对栈的消耗。
  因此在函数较多的情况下,栈区可以显著减少数据存储区消耗量!
  5.2栈区的不可替代性
  前文讲了栈区可以显著减少内存消耗量,拿我们在不考虑内存消耗的情况下, 取消栈区只使用静态区  ,这样可以让数据存储器中的分区更单一,更方便管理,这样可以吗?
  假设现在有如下函数: /****************************************** * @作者     : liwei    * @Github 	: liyinuoman2017 *******************************************/ double factorial(double n) {     double s;     if(n >= 2)     {         s = n*factorial( n - 1 );     }     else if(n ==1)     {         s = 1;     }     return s; }
  factorial是一个递归函数实现阶乘功能,递归函数将调用自身。这样情况下递归函数中的变量如果使用静态变量将无法正常运行,递归函数中的变量只能使用局部变量,局部变量存储在栈区 。
  假设现在有如下代码: /****************************************** * @作者     : liwei    * @Github 	: liyinuoman2017 *******************************************/ void main(void) {	 	... 	sum(); }  int sum(int a , int b) { 	int c; 	c = a + b; 	return c; }  /*  中断函数  */ void irq_handler(void) {	 	... 	sum();	 }
  假设main函数在 调用sum函数期间 , 处理器产生了中断 ,此时程序跳转并执行irq_handler函数,在irq_handler函数中也调用了sum函数, 此时sum函数出现被重复调用两次 ,sum函数中的变量也必须使用局部变量。不仅仅只有递归函数中的变量必须使用局部变量,在一些特殊使用场景下只能使用局部变量,此时体现了栈区的不可替代性。
  综上所述,使用栈区可以节省数据存储空间,同时一些特殊使用场景只能使用栈区。
  6.every coin has two sides
  前文指出栈区的使用有不可替代性,同时可节省数据存储空间。 那么栈区使用是不是就是一种完美的方案呢?
  every coin has two sides(每个硬币都有两面)
  任何事物都有两面性,有优点就会有缺点。 使用栈的缺点是:栈溢出!
  相信大家都遇到过栈溢出的情况吧,栈溢出会让程序产生表现形式多变的BUG,这类型的BUG通常让人很难定位。
  下图展示了栈溢出的情况:
  由图可知当栈溢出时,可能会错误的修改静态区的变量的数值,导致程序出现BUG。而且栈溢出后修改的静态区的数值体现出随机性,因此会让程序产生表现形式多变的BUG。
  既然栈溢出会造成严重BUG,那么有没有方法来检测栈溢出?
  第一种方法 是采用的做法是栈区初始化时,在栈的末端 填充固定的标记字符 (比如0x5a5a5a5a), 如果发生了"栈溢出",那么栈区末端填充的标记字符则有可能会被更改。
  这样通过检测栈区末端标记字符是否被更改来判断是否有"栈溢出",这种检测方法并不是100%有效的,是因为末端的标记字符有可能被跳过。
  第二种方法 是通过栈基地址和当前栈地址 计算当前栈的大小 ,栈基地址减当前栈地址即可求出栈的使用大小, 若计算出的栈大小大于系统分配的栈大小则"栈溢出"  。
  创作不易希望朋友们点赞,转发,评论,关注。
  您的点赞,转发,评论,关注将是我持续更新的动力
  作者:李巍
  Github:liyinuoman2017
  CSDN:liyinuo2017
  今日头条:程序猿李巍

施一公我们感知到的世界不是客观的,宇宙真实存在形式人感知不到每天耕耘最有趣最实用的心理学中科院院士施一公曾经在演讲中说过,我们所感知的世界并不是客观的,大多数宇存在形式我们都感知不到。听完这句话,你可能会觉得施一公院士在危言耸听或是胡说八道人生不要自恋,你的悲伤不是别人的悲伤,倾诉辨虚伪!不要随便和别人倾诉你当下经历的苦难和悲伤,别人不一定能体会你的伤痛和感受,人家也没有义务和你产生共鸣,感同身受非泛泛之交可以有的!去年八月份我亲哥哥突然在内蒙古的打工路上倒下了,再地震为什么多发生在夜间和初一十五地震的危害不仅在于突发性强,而且发生在夜间,对受害者更是雪上加霜。比如这次土耳其大地震,就发生在当地时间凌晨4点。据统计,85年我国境内共发生25次5级以上的地震,竟有20次发生在2700元左右高性价比的三款手机款款不让人失望您在阅读前请点击上面的关注二字,后续会第一时间为您提供更多有价值的相关内容,感谢您的支持。现在的手机已经进入了一个越来越智能化的时代,手机更新迭代速度犹如坐过山车一样快,一年更一代MySQL表设计一二三四五范式BC范式与反范式详解!MySQL的库表设计,在很多时候我们都是率性而为,往往在前期的设计中考虑并不全面,同时对于库表结构的划分也并不明确,所以很多时候在开发过程中,代码敲着敲着会去重构某张表结构,甚至大如何看待青少年玩手机青少年玩手机嗨,大家好,我是定格风景画,很高兴又和大家见面了,今天聊点什么呢?看我的标题就知道了,咱们也不废话了,开门见山吧!电话的发明方便了人们之间沟通,后来随着科技的发展进步出蔚来手机渲染图公布!圆形镜头模组三角镜头分布手机中国新闻此前不久,蔚来联合创始人总裁秦力洪表示,蔚来手机将在今年第二季度开启内测,预计会在今年三季度正式向社会发售。随后,更是有博主爆料,蔚来城市活动已经开始抽取全国首批蔚来手雷霆咆哮3雷鸣战斗机ICC。MC雷鸣战斗机是ICC为地球某东方文明打造全新空战思维的一款重要空中战斗单位,代号斩首行动,将奔雷7咆哮者无人机雷鸣战斗机,组成更有攻击层次的作战逻辑。雷鸣无人机简称大T,航海王热血航线长环岛的宝藏位置航海王热血航线长环岛的宝藏记录着宝藏埋藏地点的藏宝图,地形似乎被制作者隐藏了,无法识别,只能隐约看见上面的文字藏宝图信息藏宝图信息想要获得宝藏的话,就去那个神奇的岛屿吧,那里有长长用版本下水道兰陵王,也能上巅峰赛第一?召唤师峡谷要变天了?在王者荣耀这款MOBA竞技手游当中,以节奏带动为主的打野位,一直都是大部分玩家心目中,最适合上分的位置。一名个人技术非常出色,又具备了良好大局观的打野玩家,不仅能在游戏中Carry直播预告你听得懂孩子的求助信号吗?心理专家做客新湖南为你答疑湖南日报2月9日讯(全媒体记者周阳乐谭雨欣)开学了,孩子是否出现厌学焦虑失眠精力不集中等问题?怎么帮助孩子更好地进入学习状态?如何捕捉孩子的求助信号,并且给予正确的引导和鼓励?2月
澎湃中原的商旅势能河南省旅游饭店协会实训基地落户新时代广场后疫情时代,如何吸引流量提高业绩是众多酒店最关注的焦点与需求。为更好的服务新时代广场时代领寓业主的酒店化运营,4月10日,河南省旅游饭店业协会实训基地正式落户华润置地新时代广场。华天生没子宫,对口水过敏,这6位明星被隐疾折磨多年文阅栀编辑阅栀观众眼中的明星,要么是在影视剧里演绎百味人生,要么在镁光灯下光鲜亮丽。但长年不规律的作息时间,不少人的身体状况早已亮红灯。比如功夫巨星成龙,右耳听力只有两成,在有回声双子宫双宫颈孕妇早产在红房子医院顺利分娩图说一名重2420g的小公主诞生采访对象供图4月4日夜晚,红房子医院黄浦院区急诊来了一位孕36周的孕妇芸芸(化名),她的到来让接诊医生朱梦晗马上紧张起来。和一般的孕妇不同,芸芸不仅口水能过敏,天生没子宫,这6位明星的隐疾,困扰他们许久有那么几年,每每出席活动,冯小刚都会用一顶鸭舌帽遮住自己的头脸。大家都明白,此举是为了遮住蔓延到他脸上的白斑。很多影迷都为他留言,提供治疗白癜风的祖传秘方或偏方,不过对于这个病,冯河南省交通旅游融合发展策略之模型测算与对比分析龙志刚专栏作者曹艺林河南省交通规划设计研究院股份有限公司,河南交通运输战略发展研究院编辑曹艺林校核张菁菁审核龙志刚2016年以来,国家各部委相继出台了多个规划与政策文件,从顶层推动交通与旅游河南省文旅今年将实施五大战略十大工程网红打卡地河南博物院要上新原标题省文旅今年将实施五大战略十大工程网红打卡地河南博物院要上新在黄河边一处风景区,游客在欣赏黄河美景。游客在参观大河村遗址博物馆大河报豫视频记者刘瑞朝文白周峰摄影今年,河南的文物常见胃炎舌象分析,附有成药参考胃炎多见有食欲不振嗳气反酸烧心,吃东西不敢乱吃,否则就容易胃部不舒服胃胀难受等,很多人看到胃炎就一味乱用药缓解,缓解是缓解了,可经常反反复复,其实这是没有正确辩证用药。来看一个比较拉波尔塔输赢是常见的事情但诺坎普坐满对手球迷简直是耻辱北京时间15日凌晨,巴萨欧联杯14决赛次回合主场23不敌法兰克福,总比分34遗憾出局,这也是巴萨今年首次在诺坎普输球。巴萨俱乐部主席拉波尔塔赛后接受采访今天我们输了,从欧联杯出局了小小舌头察健康,教你6种常见脾病自查,简单实用中医临床上,四诊和参,而舌诊有着独特的魅力,同时也是辅助诊断以及鉴别脏腑气血盛衰的一种方法,有言说道舌是心之苗,脾之外候,苔是由胃气所生,同时舌也是上演脏腑盛衰的一部电影,中医通过游记一魂牵五台山情系佛母洞大方广佛华严经中载东北方有处,名清凉山。从昔以来,诸菩萨众,于中止住。现有菩萨,名文殊师利,与其眷属,诸菩萨众,一万人俱,常在其中,而演说法。佛说文殊师利宝藏陀罗尼经中说尔时,世尊年龄越大越要忌口?人过60岁,这3款酒能不喝就不喝,别不当回事坊间有流言饭后一根烟,赛过活神仙,饭后一杯酒,活到九十九!当然,这句话仅限于娱乐,可别用于真实场景。否则,后患无穷哦!或许有人冒失而问大家都这么说,那肯定就是真理!在此有必要诠释下