举例说明进制转换器过程中堆栈的作用

数据结构 运用链栈实现10进制到8进制和16进制的转换运用堆栈实现逆波兰表达式源程序_百度文库
您的浏览器Javascript被禁用,需开启后体验完整功能,
享专业文档下载特权
&赠共享文档下载特权
&10W篇文档免费专享
&每天抽奖多种福利
两大类热门资源免费畅读
续费一年阅读会员,立省24元!
数据结构 运用链栈实现10进制到8进制和16进制的转换运用堆栈实现逆波兰表达式源程序
&&运用链栈实现10进制到8进制和16进制的转换 运用堆栈实现逆波兰表达式
入栈的顺序和出栈的顺序对程序的影响,计算方法是“除N取余法”,并且把得出的结果逆序输出,恰好满足栈先入后出的结构顺序。需要注意逆波兰表达式的执行顺序。
阅读已结束,下载本文需要
定制HR最喜欢的简历
下载文档到电脑,同时保存到云知识,更方便管理
加入VIP
还剩4页未读,
定制HR最喜欢的简历
你可能喜欢欢迎加入我们,一同切磋技术 &
用户名: &&&
密 码: &
共有 12344 人关注过本帖
标题:用栈实现进制转换
等 级:蜘蛛侠
帖 子:621
专家分:1069
结帖率:100%
&&已结贴√
&&问题点数:20&&回复次数:7&&&
用栈实现进制转换
程序代码:/*运用栈实现进制转换*/
#include&stdio.h&
#include&malloc.h&
typedef char DataType
#define STACK_SIZE 100
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&// 构造栈
typedef struct{&&&&&&&&&&&&&&&&&&&&&&&
&&& DataType *
&&& DataType *
&&& int stack_
void init(seqstack *s)&&&&&&&&&&&&&&&&&// 初始化
&&& s-&base=(seqstack *)malloc(STACK_SIZE*sizeof(DataType));
&&& if(!s-&base) exit(-<font color=#);
&&& s-&top=s-&
&&& s-&stack_size=STACK_SIZE;
int IsEmpty(seqstack *s)&&&&&&&&&&&&&&&// 判断栈空
&&& return s-&base==s-&
int IsFull(seqstack *s)&&&&&&&&&&&&&&& // 判断栈满
&&& return s-&top-s-&base==STACK_SIZE-<font color=#;
void Push(seqstack *s,char ch)&&&&&&&&&// 进栈
&&& if(IsFull(s))
&&&&&&&&printf(&overflow\n!&);
&&&&&&&&exit(<font color=#);
&&&&&&&&*s-&top++=
char Pop(seqstack *s)&&&&&&&&&&&&&&&&&// 出栈
&&& if(IsEmpty(s))
&&&&&&&&printf(&栈空\n&);
&&&&&&&&exit(<font color=#);
&&& return *--s-&
char Top(seqstack *s)&&&&&&&&&&&&&&&&&& // 取栈顶元素
&&& if(IsEmpty(s))
&&&&&&&&printf(&栈空\n&);
&&&&&&&&exit(<font color=#);
&&& return *(s-&top-<font color=#);
void conversion(int a,int b)&&&&&&&&&&&&&&&&&&&&&// 实现进制转换
&&& seqstack *s;
&&& init(s);
&&& while(a)
&&&&&&&&Push(s,a%b);
&&&&&&&&a=a/b;
&&& while(!IsEmpty(s))
&&&&&&&&i=Pop(s);
&&&&&&&&printf(&%d&,i);
int main(int argc,char *argv[])
&&& int a,b;
&&& printf(&please input the data you want to conversion\n&);
&&& scanf(&%d&,&a);
&&& printf(&please input the scale you want to convert to\n&);
&&& scanf(&%d&,&b);
&&& conversion(a,b);
&&& return <font color=#;
请问为何编译提示出错
搜索更多相关主题的帖子:
等 级:贵宾
威 望:103
帖 子:3282
专家分:12654
&&得分:20&
#include&stdio.h&
#include&malloc.h&
typedef char DataT//缺结束分号,已修改
#define STACK_SIZE 100
typedef struct{&&&&&&&&&&&&&&&&&&&&&&&
&&&&&DataType *
&&&&&DataType *
&&&&&int stack_
void init(seqstack *s)
&&&&&s-&base=(DataType *)malloc(STACK_SIZE*sizeof(DataType));//强制转换的类型不对,已修改
&&&&&if(!s-&base) exit(-1);
&&&&&s-&top=s-&
&&&&&s-&stack_size=STACK_SIZE;
int IsEmpty(seqstack *s)
&&&&&return s-&base==s-&
int IsFull(seqstack *s)
&&&&&return s-&top-s-&base==STACK_SIZE-1;//这里没必要减1
void Push(seqstack *s,char ch)
&&&&&if(IsFull(s))
&&&&&&&&&printf(&overflow\n!&);
&&&&&&&&&exit(1);
&&&&&&&&&*s-&top++=
char Pop(seqstack *s)
&&&&&if(IsEmpty(s))
&&&&&&&&&printf(&栈空\n&);
&&&&&&&&&exit(1);
&&&&&return *--s-&
char Top(seqstack *s)// 取栈顶元素,你没用到这个函数
&&&&&if(IsEmpty(s))
&&&&&&&&&printf(&栈空\n&);
&&&&&&&&&exit(1);
&&&&&return *(s-&top-1);
void conversion(int a,int b)
&&&&&//不应声明成指针,已修改
&&&&&init(&s);//已修改
&&&&&while(a)//当a本身即为0时没有输出,考虑过a为负数的情况没?
&&&&&&&&&Push(&s,a%b);//已修改
&&&&&&&&&a=a/b;
&&&&&while(!IsEmpty(&s))//已修改
&&&&&&&&&i=Pop(&s);//已修改
&&&&&&&&&printf(&%d&,i);//当基数大于9时输出结果不对。改成printf(&%x&, i);可以实现16进制内的输出。改成printf(&%c&, (i & 10) i+'0' : i - 10 + 'A');可以实现36进制内的输出
&&&&&free(s.base);//释放堆,添加
int main(int argc,char *argv[])
&&&&&int a,b;
&&&&&printf(&please input the data you want to conversion\n&);
&&&&&scanf(&%d&,&a);
&&&&&printf(&please input the scale you want to convert to\n&);
&&&&&scanf(&%d&,&b);
&&&&&conversion(a,b);
&&&&&return 0;
重剑无锋,大巧不工
等 级:贵宾
威 望:103
帖 子:3282
专家分:12654
关于进制转换,我在福建农林大学OJ中也做过这样一个题。下面是我的AC代码,其中b是要转换的进制基数,n是代转换的十进制数程序代码:#include &stdio.h&
int main()
&&& int b, n, a[<font color=#], i,
&&& scanf(&%d %d&, &b, &n);
&&& if(n == <font color=#){ printf(&<font color=#\n&); return <font color=#;}
&&& for(i = <font color=#; n & <font color=#; i++)
&&&&&&&&a[i] = n %
&&&&&&&&n /=
&&& for(j = <font color=#; j & j++)
&&&&&&&&printf(&%x&, a[i - j - <font color=#]);
&&& printf(&\n&);
&&& return <font color=#;
重剑无锋,大巧不工
等 级:蜘蛛侠
帖 子:621
专家分:1069
这个为什么不可以申明为指针 seqstack *s?
等 级:贵宾
威 望:103
帖 子:3282
专家分:12654
可以,但你原来的S是悬空的,没有指向实体
重剑无锋,大巧不工
等 级:新手上路
回复 2楼 beyondyf
若考虑a为负数的情况该如何改呢?
等 级:新手上路
回复 2 楼 beyondyf
为什么运行之后会有这样的错: error C2065: 'exit' : undeclared identifier
感觉没问题了啊。
等 级:新手上路
回复 7 楼 jmyh
盆友,少了#include &stdlib.h&这个头文件
版权所有,并保留所有权利。
Powered by , Processed in 0.040542 second(s), 7 queries.
Copyright&, BCCN.NET, All Rights Reserved>> 在c++中用堆栈实现十进制到任意进制转换
在c++中用堆栈实现十进制到任意进制转换
所属分类:
下载地址:
ten2any.rar文件大小:8.62 kB
分享有礼! 》
请点击右侧的分享按钮,把本代码分享到各社交媒体。
通过您的分享链接访问Codeforge,每来2个新的IP,您将获得0.1 积分的奖励。
通过您的分享链接,每成功注册一个用户,该用户在Codeforge上所获得的每1个积分,您都将获得0.2 积分的分成奖励。
在c++中用堆栈实现十进制到任意进制转换-in C++ using the metric system stack to achieve arbitrary bases conversion
Sponsored links
源码文件列表
温馨提示: 点击源码文件名可预览文件内容哦 ^_^
ten2any.dsw537.00 B22-05-05 11:56
ten2any.ncb33.00 kB23-05-05 09:59
ten2any.plg1.24 kB22-05-05 11:58
ten2any.dsp4.20 kB22-05-05 11:56
1.06 kB22-05-05 11:58
ten2any.opt47.50 kB23-05-05 09:59
&ten2any&0.00 B22-05-05 11:56
(提交有效评论获得积分)
评论内容不能少于15个字,不要超出160个字。
评价成功,多谢!
下载ten2any.rar
CodeForge积分(原CF币)全新升级,功能更强大,使用更便捷,不仅可以用来下载海量源代码马上还可兑换精美小礼品了
您的积分不足,优惠套餐快速获取 30 积分
10积分 / ¥100
30积分 / ¥200原价 ¥300 元
100积分 / ¥500原价 ¥1000 元
订单支付完成后,积分将自动加入到您的账号。以下是优惠期的人民币价格,优惠期过后将恢复美元价格。
支付宝支付宝付款
微信钱包微信付款
更多付款方式:、
您本次下载所消耗的积分将转交上传作者。
同一源码,30天内重复下载,只扣除一次积分。
鲁ICP备号-3 runtime:Elapsed:205.638ms - init:0.1;find:1.2;t:0.6;tags:11.0;related:109.1;comment:0.3; 27.69
登录 CodeForge
还没有CodeForge账号?
Switch to the English version?
^_^"呃 ...
Sorry!这位大神很神秘,未开通博客呢,请浏览一下其他的吧实训2 利用栈实现任意进制的转换_百度文库
您的浏览器Javascript被禁用,需开启后体验完整功能,
享专业文档下载特权
&赠共享文档下载特权
&10W篇文档免费专享
&每天抽奖多种福利
两大类热门资源免费畅读
续费一年阅读会员,立省24元!
实训2 利用栈实现任意进制的转换
&&快来看看吧
阅读已结束,下载本文需要
定制HR最喜欢的简历
下载文档到电脑,同时保存到云知识,更方便管理
&#xe64e;加入VIP
还剩3页未读,
定制HR最喜欢的简历
你可能喜欢内存中的堆和栈到底是什么 - 简书
内存中的堆和栈到底是什么
文章也同时在更新
网络上关于内存中各区段作用的文章有很多,但不得不吐槽一下,这些文章大多相互引用,内容大同小异,没有把问题讲解清楚。
因此,笔者想通过本文,借助汇编的知识,深入底层讲解内存模型。本文的结构如下:
程序在内存中的存储模型
编程过程中常见的几类变量所在的位置和作用
堆和栈的细节
起到抛砖引玉作用的底层原理(这意味着你需要自己去深入研究才能真正理解清楚)
前三小节是浅尝辄止地引题,详细原理请见第四小节,最后在第五小节笔者给出了可实际操作的方法,帮助大家更直观地理解。文章可能较长,请坚持读完,或者择篇章阅读。
网上的资料
首先,笔者罗列出一些质量尚可的博客,大家可以先阅读一下。之后笔者会针对大家可能存在的疑惑,从底层来一一讲解清楚。
网上很多文章都引用到了下面这段代码:
int a = 0; //全局初始化区
char *p1; //全局未初始化区
char s[] = "abc"; //栈
char *p2; //栈
char *p3 = "123456"; //在常量区,p3在栈上
static int c =0; //全局(静态)初始化区
p1 = (char *)malloc(10); //堆
p2 = (char *)malloc(20);
并且如果你搜索关键词“内存堆栈图”,将很容易找到下面这张图:
这也是笔者在查阅资料时很不满的地方,许多文章互相引用,内容雷同,却没有把问题的本质讲清楚。
因此,笔者将基于上述代码以及这张图,这两个最常被引用的东西,来把原理讲清楚。
程序在内存中的模型
注:本文所指的程序皆在用户空间运行,即不涉及操作系统类程序和驱动程序。
目前流行的那几种高级语言,归根到底,底层实现的思路都是差不多的。而且当今以Intel主流的CPU架构(虽然也有ARM),其设计理念也是一脉相承的。
要讲清楚内存模型,我们就要深入底层涉及到汇编,很多高级语言都会经历翻译到汇编这一中间过程,汇编可以直观地使用机器指令,是最接近的底层的语言。
在一个汇编程序中,常常把一个用户空间程序按习惯分为三个段:.data段,.bss段,.text段。
.data段包含了已经初始化了的数据项,这些数据在程序开始运行前就拥有自己的值,这些值是可执行文件的一部分,当可执行文件被加载到内存中用于执行时,这些数据也被加载到内存中。
定义的初始化数据越多,可执行文件就越大,运行它的时候也就需要更长的时间才能将它们从磁盘加载到内存。
一些全局或者静态的,且经过定义初始化过的变量,就属于该段。例如下面代码中的a,指针p以及b三个变量:
int a = 2;
int *p = &a;
int main ()
static int b = 1;
并不是所有数据项在程序开始之前都拥有值,例如你可以定义一个缓冲区来存在某些数据,这个缓冲区是.bss段中定义的。
分别定义.data段与.bss段中的数据,它们一个重要的区别就是:.data段中的数据会添加到可执行文件的大小上,而.bss段中的则不会。即便你给.bss段定义一个1M字节的缓冲区,其最终可执行文件大小也几乎不变(除了大约50个字节用于描述外)。
程序在加载时知道哪些数据项没有初值,它会为这些数据项分配空间,而具有初值的数据线会与其初值一同读入。
一些全局或者静态的,且未经过初始化的变量,属于.bss段。例如上文中.data段段的三个变量,如果不进行初始化,就会存储在本段中。
以上两个段都是源程序所需要的数据,而真正组成程序的机器指令则存放在.text段中。一般情况下,在.text段中不进行数据项的定义。.text段包含名为标号(label)的符号,这些符号用于标识跳转和调用程序代码位置。
程序内存中的堆栈
先附上笔者在学习汇编时的一张笔记图,字比较丑,望各位见谅。
程序内存中的堆栈附笔记
通过该笔者图大家能够大概了解内存中上述三个段的位置。至于其中的堆栈以及笔记的含义请继续阅读后文。
编程过程中常见的几类变量
观察最开头经常被引用的那段代码,其中涵盖了几类最常见的变量以及其对应的存储位置。在上一小节中,我们已经说明了全局变量和静态变量存储的位置取决于是否进行过初始化。对于堆栈的解释我们留到下一小节。这里我们着重讲解文字常量区。
文字常量区
考虑如下代码:
char *p3 = "123456"; //在常量区,p3在栈上
这个文字常量区是什么?显然它与字符串存放有关。所谓字符串是指位于连续内存区域中的一个字符序列。字符串通过在起始处关联一个标号来进行定义。在汇编中,常见的字符串定义如下:
MSG: db "something"
它是位于.data段中的。和.data段中的所有变量一样,它也是一种已经初始化的数据:带有一个值,而不仅仅是一个在将来某时刻用于存放数据的内存空间。MSG标号和DB指令在内存中指定一个字节作为字符串的起点,而字符串中的字符数则告诉汇编编译器为该字符预留多少个字节的存储空间。
但高级语言中的字符串可能要比这里复杂一点,以C语言为例,针对printf函数中包含的字串。笔者认为其存储于.data段和.text段之间的一个名叫.rodata段的地方。即那张常见的“堆栈内存图”中底部绿色的“只读区”。
大家可以发现,现在引出了更多的背后细节。因此,更为深入的说明我会留到第四个小节:底层的原理。
堆和栈的细节
下面进入第三小节,讲解堆和栈,这也是最开头代码中仍未涉及的两种变量存储位置。注意:我们在汇编中常说的堆栈,其实是栈,并不包含堆。
在此之前,推荐大家看一下stackoverflow的这个问答
栈由系统管理。但是为什么呢?
首先,栈是一个后进先出(LIFO)结构。当把数据放入栈时,我们把数据push进入;当从栈取出数据时,我们把数据pop出来。栈随着数据被压入或者弹出而增长或者减小。最新压入栈的项被认为是在“栈的顶部”。当从栈中弹出一个项时,我们得到的是位于栈最顶部的那一个。就像给弹夹上子弹,只能在顶部进行操作。
在x86体系中,栈顶由堆栈指针寄存器ESP来标记,它是一个32位寄存器,里面存放着最后一个压入栈顶的项的内存地址。正因为有它,我们才能够随时操作到需要的项。需要注意的是,栈顶是朝着地内存方向增长的。
再来看我拍的照片,为于.bss段和栈之间有一段空余内存,C程序经常使用这种剩余内存空间来为那些为于堆内存中的,“已经在运行中”的变量分配空间。我们常说的堆就存在于这里。
二者分别存储什么以及原因
可以看到栈有一个ESP寄存器管理,从底层就实现了一种“自动化”,而堆似乎并没有额外的东西来帮助管理。
此外,栈的大小需要有一定的限制,栈的增长是向低地址扩展,如照片中看到的,如果栈不断地增加,很可能会与.bss段发生碰撞,这是不堪设想的,系统会发出错误并终止程序。
栈应该被看成一个短期存储数据的地方,存在在栈中的数据项没有名字,只是按照后进先出来操作罢了。栈经常可以用来在寄存器紧张的情况下,临时存储一些数据,并且十分安全。当寄存器空闲后,我们可以从栈中弹出该数据,供寄存器使用。这种临时存放数据的特性,使得它经常用来存储局部变量,函数参数,上下文环境等。
相反,堆相对于栈,更加强调需要进行控制。常见的就是我们手动申请,手动释放。因此可以分配更大的空间,但开销也会更多。
上面三个小节对于底层原理都是浅尝辄止,一上来就讲得很深入,会增加阅读负担。但在这一小节,我们必须讲一些底层的东西。不过笔者必须提前声明,虽然我们会涉及很多底层的知识,但对于整个计算机系统,这仍旧是冰山一角的知识。笔者在这里更多地是起到抛砖引玉的作用,完全讲解清楚,可能需要一本书的篇幅,而且笔者水平也很有限。这意味着如果你阅读了本文,有所启发想要一探究竟,可能就真的需要自己去探索了。
在这里,笔者推荐一本书:。我很诧异这本书竟然出到第三版了,注意第三版针对64位CPU,学习的话还是在32位下比较方便,因此推荐第二版。
可执行目标文件
程序在运行前以可执行文件的形式存储在磁盘中,我们先来看一下这张图:
典型的ELF可执行目标文件
ELF格式是类UNIX系统中可执行文件的常见格式,在众多表项中我们重点关注:.text,.rodata,.data,.bss这四个小段(节)。可以看到.text和.rodata属于只读存储器段(代码段),而.data,.bss属于可读可写存储器段(数据段)。下面具体说明这四个小段。
存放已编译程序段机器代码。
存放只读数据,如C语言中printf语句中的格式串和开关语句的跳转表。
所谓开关语句的跳转表,一个典型的例子就是switch(开关)语句的汇编实现,其使用了数组来映射代码块的地址,以此构成一张跳转表,相关的内容存储于只读数据中。
已初始化的全局C变量。局部C变量在运行时保存在栈中,既不出现在.data中,也不在.bss中。
未初始化的全局C变量。如前文汇编语言讲解中提到的,它在目标文件中不占据实际空间,仅仅是一个占位符。目标文件格式区分初始化和未初始化变量是为了空间效率:在目标文件中,未初始化变量不需要占据任何实际的磁盘空间。
值得一提的是,.bss原本是IBM704汇编语言(大约在1957年)中Block Started by Simple指令的首字母缩写,并沿用至今。不过在今天,我们只需要记住区别.data和.bss的最简单的方法就是把.bss看成是“更好地节省空间”(Better Save Space)的缩写!
有一些特例
标记有static静态标志的局部变量不在栈中管理,而是根据有无初始化,在.data或者.bss中。
对于GCC编译器,初始化为0的变量存储在.bss中。
所以说,如果想真的搞清楚来龙去脉,仍旧需要你自己去阅读各类文献。
加载可执行目标文件
可执行文件在内存中运行时,有一个运行时存储器印象,我们来看一下这其中的情况,如下图:
Linux 运行时存储器映像
这张图涵盖了本文所讲的大多数知识点。相比于前文的那张汇编语言内存图,更加细分了。
代码段总是从地址0x处开始。
数据段在接下来的下一个4KB对齐的地址处。
运行时堆在读/写段(数据段)之后接下来的第一个4KB对齐的地址处,并通过malloc库往上(高地址方向)增长。
中间还有一个段是为共享库(shared library)保留的。
用户栈总是从最大的合法用户地址开始,向下增长(低地址方向)
栈上方的段是为操作系统驻留存储器部分(也就是内核)的代码和数据保留的。
当程序开始运行时,加载器在可执行文件中段头部表的指引下,将可执行文件的相关内容拷贝到代码段和数据段。
上述的一些名词,比如共享库,其含义可能需要你自己去研究。Tips:Windows的.DLL。另外,笔者在参考各类文献时发现,上述诸如数据段,data段等名字经常包含不同的含义,且经常一个概念有多种说法。例如只读段又可以被认为是代码段。这里大家需要注意我们所说的数据段不是指data段,而是data段和bss段。
其实完全细分的名称会与操作系统和CPU架构有关,笔者在这里只能针对共通的地方加以概括。
动态存储器分配
这里重点讲一下堆。
动态存储器分配维护这一个进程的虚拟存储器区域,称为堆(heap)。我们假设堆是一个请求二进制零的区域,它紧接在未初始化的.bss区域后开始,并向上(高地址方向)生长。对于每一个进程,内核维护这一个变量brk(读作"break"),它指向堆堆顶部。如下图:
分配器将堆视为一组不同大小的块(block)的集合来维护。每一个块就是一个连续的虚拟存储片(chunk),要么已分配,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲的块可以用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配的状态,知道它被释放。这种释放要么是应用程序显式执行的,要么是存储器分配起自身隐式执行的。
显式分配器(explicit allocator),要求应用显式地释放任何已分配的块。如C中的malloc和free。C++中的new和delete。
隐式分配器(implicit allocator),要求分配器检测一个已分配的块何时不再被程序所使用,就去释放这个块。隐式分配器也叫做垃圾收集器(garbage collector),而自动释放未使用的且已被分配的块的过程叫做垃圾收集(garbage collection)。不用我说,你们也可能已经想到了Java的垃圾回收机制。
可见堆也并不是非要人工手动去管理的,文章最开始的一些说法确实是值得推敲的。
对于堆的组织方式,笔者略提一下其中的一种方式:我们可以将堆组织为一个连续的已分配块和空闲块的序列,我们称这种结构为隐式空闲链表。空闲块通过头部中的大小字段隐含地连接着,分配器可以通过遍历堆中的所有块,从而间接遍历整个空闲块的集合。如下图:
隐式空闲链表
此外,笔者还想顺带说一个很容易出问题的地方:对于C语言malloc的内存区域,通过一个指针去访问,当该片内存被free后,请务必将无效指针设为NULL!请务必将无效指针设为NULL!请务必将无效指针设为NULL!(在iOS对应的OC中,请将对象指针设为nil。)
之所以要这样,简而言之,在分配器的实现细节中,在调用free返回之后,指向分配区域的指针仍会指向被释放了的块(野指针)。现在,该块已经实效,如果再通过该野指针去访问,会出现可怕的后果。因此应该确保在该块被一个新的malloc调用重新初始化之前,不再使用该野指针,最好的防治误用的做法就是给指针置零。
分配器的设计和实现是复杂的,想要一探究竟还需要你自己去研究。
过程和栈帧
这里讲述最后一点:栈。
C语言中的函数,对应汇编中的过程。一个过程调用包括将数据(以过程参数和返回值的形式)和控制从代码的一部分传递到另一部分。另外,它还必须在进入时为过程的局部变量分配空间,并在退出时释放这些空间。上述的数据传递,局部变量的分配和释放通过操纵程序帧来实现。
程序用程序栈来支持过程调用。机器用栈来传递过程参数,存储返回信息,保存寄存器用于以后恢复,以及本地存储。为单个过程分配的那部分成为栈帧(stack frame)。下图描绘了栈帧的通用结构,最顶端的栈帧以两个指针界定,寄存器%ebp为帧指针,而寄存器%esp为栈指针。当程序执行时,栈指针可以移动,因此大多数信息访问都是相对于帧指针的。(注:%esp与ESP是同一个寄存器的不同说法而已,%ebp同理)
假设过程P(调用者)调用过程Q(被调用者),则Q的参数放在P的栈帧中。另外,当P调用Q时,P中的返回地址被压入栈中,形成P的栈帧的末尾。返回地址就是当P从Q返回时应该继续执行的地方。Q的栈帧从保存的帧指针的值(例如寄存器%ebp的副本)开始,后面时保存的其他寄存器的值。
过程Q也用栈来保存其他不能存放在寄存器中的局部变量,这样做的原因如下:
没有足够的寄存器存放所有的局部变量。和前文汇编语言部分解释的原因相同。
有些局部变量是数组或者结构,因此必须通过数组或者结构引用来访问。
要对一个局部变量使用地址操作符'&',我们必须能够为它生成一个地址。
另外,Q也会用栈帧来存放它调用的其它过程的参数。参数n位于相对于%ebp偏移量为4+4n字节的地方。较大的参数(如结构体和较大的数字格式)需要栈上更大的区域。
正如前文所讲,栈向低地址方向增长。栈指针%esp指向栈顶元素,可以用push存入数据,用pop取出数据。将栈指针的值减小适当的大小可以分配没有指定初始值的数据的空间(加入数据栈顶向低地址方向移动)。类似地,可以通过增加栈指针来释放空间(取出数据栈顶向高地址方向移动)。
实验环节(更新于)
纯理论的东西可能让人没有实感,对于各区段在内存中的模型,笔者也一直思索该如何以编程的方式展现,今天终于找到了一个好方法。
首先,请确保你有一个Linux或类Unix的系统环境,我们需要用一些命令。笔者是在Mac上实验,发现Mac的命令有点差异,于是ssh到了自己的Ubuntu服务器。
考虑下述代码:
#include &stdio.h&
int a = 0; //全局初始化区
char *p1; //全局未初始化区
int main()
char s[] = "abc"; //栈
char *p2; //栈
char *p3 = "123456"; //在常量区,p3在栈上
static int c =0; //全局(静态)初始化区
p1 = (char *)malloc(10); //堆
p2 = (char *)malloc(20);
其实就是那段引用烂了的代码,笔者补全了int main()和return 0。我们以此为蓝本,修改一些代码来观察生成的可执行文件的结构,以此让大家对各区段的作用有个清晰的认识。
复制粘贴编辑,gcc编译完成后,笔者将其命名为origin。接着在命令行中键入:
& size origin
可以看到如下结果:
hex filename
7b8 origin
关注前三个表项,列出了各区段的大小,请记住这些大小。
修改一(加入全局变量并初始化)
我们在main()函数前加入一个全局数组,并初始化一下,代码如下:
#include &stdio.h&
int a = 0; //全局初始化区
char *p1; //全局未初始化区
int arr[1000] = {233}; // 修改的代码在这里,全局数组已初始化
int main()
char s[] = "abc"; //栈
char *p2; //栈
char *p3 = "123456"; //在常量区,p3在栈上
static int c =0; //全局(静态)初始化区
p1 = (char *)malloc(10); //堆
p2 = (char *)malloc(20);
同样编译并执行size命令,笔者将其命名为addToDataSection,得到如下结果:
hex filename
1768 addToDataSection
注意到data段大小增加了4000字节,原因就是全局数组在源码编译后,会直接增加到生成的可执行文件中,1000个int在32位下就是10004B = 4000B*。
修改二(加入全局变量但不初始化)
接下来,对于增加的全局数组,去掉其初始化操作,代码如下:
#include &stdio.h&
int a = 0; //全局初始化区
char *p1; //全局未初始化区
int arr[1000]; // 全局数组不进行初始化
int main()
char s[] = "abc"; //栈
char *p2; //栈
char *p3 = "123456"; //在常量区,p3在栈上
static int c =0; //全局(静态)初始化区
p1 = (char *)malloc(10); //堆
p2 = (char *)malloc(20);
编译,命名为addToBssSection,执行size命令,结果如下:
hex filename
1780 addToBssSection
可以看到bss段增加了4000字节,别急,这并不意味着bss段增加的数组会作用于生成的可执行文件,还记得上文说过的吗?bss段并不增加可执行文件大小,只是加入少许记录信息。我们ls三个文件即可看到区别:
-rwxrwxr-x 1 ubuntu ubuntu 8658 Dec 10 18:53 origin*
-rwxrwxr-x 1 ubuntu ubuntu 12720 Dec 10 18:55 addToDataSection*
-rwxrwxr-x 1 ubuntu ubuntu 8695 Dec 10 18:56 addToBssSection*
可以看到bss段段增加并未显著增大可执行文件的大小,只有data段才会有所影响,增加了大约4000字节。
修改三(加入局部变量)
在这里,我们把全局数组移入main()函数中,代码如下:
#include &stdio.h&
int a = 0; //全局初始化区
char *p1; //全局未初始化区
int main()
char s[] = "abc"; //栈
char *p2; //栈
char *p3 = "123456"; //在常量区,p3在栈上
static int c =0; //全局(静态)初始化区
p1 = (char *)malloc(10); //堆
p2 = (char *)malloc(20);
int arr[1000] = {233};// 内部数组
同样编译命名为addLocalVariable,执行size命令,结果如下:
hex filename
800 addLocalVariable
可以看到data段和bss段都没有什么变化,这说明局部变量不存储于这两个段,同时我们ls来查看一下四个文件:
-rwxrwxr-x 1 ubuntu ubuntu 8658 Dec 10 18:53 origin*
-rwxrwxr-x 1 ubuntu ubuntu 12720 Dec 10 18:55 addToDataSection*
-rwxrwxr-x 1 ubuntu ubuntu 8695 Dec 10 18:56 addToBssSection*
-rwxrwxr-x 1 ubuntu ubuntu 8668 Dec 10 18:59 addLocalVariable*
可执行文件大小也几乎不变,说明局部变量不会保存在其中。
修改四(局部变量设置为静态,根据是否初始化有不同的结果)
下面我们进行最后一个修改,把上述的内部数组加上static关键词,代码如下:
#include &stdio.h&
int a = 0; //全局初始化区
char *p1; //全局未初始化区
int main()
char s[] = "abc"; //栈
char *p2; //栈
char *p3 = "123456"; //在常量区,p3在栈上
static int c =0; //全局(静态)初始化区
p1 = (char *)malloc(10); //堆
p2 = (char *)malloc(20);
static int arr[1000] = {233};// 静态数组
编译命名为addStaticVariable,执行size命令,结果如下:
hex filename
1768 addStaticVariable
仔细观察,发现结果与增加全局初始化数组是一样的,这说明带有static关键词的局部变量并不存放在栈中,如果未初始化则存在于data段。
大家可以去掉静态数组的初始化语句,编译后执行size会返回如下:
hex filename
1790 addStaticVariable
可以看到原先data段的增量转移到了bss段。同时在更改前后分别ls一下可以看到如下区别:
// 数组进行初始化,编译在data段中时,其体积计算在可执行文件中
-rwxrwxr-x 1 ubuntu ubuntu 12726 Dec 11 22:20 addStaticVariable*
// 数组未初始化,编译在bss段中时,其体积不计算在可执行文件中
-rwxrwxr-x 1 ubuntu ubuntu
8702 Dec 13 15:41 addStaticVariable*
大家可以尝试在C程序中开一个很大的局部变量数组,看看编译器会怎样提示你。
之前我曾经在Win7的VS上试过,int数组若含有超过1000个元素,编译器就总是提示编译失败。后来解决的办法是利用static关键词,将其编译进bss段。因为默认的局部变量数组存放在栈中,一下子开太大会超过Windows的限制。不过显然,生成的exe文件在执行前需要读取更多的信息。
我们可以将上述实验结果总结如下:
data段保存在目标文件中
bss段不保存在目标文件中(除了记录bss段在运行时所需的大小)
局部变量并不进入可执行文件,它们在运行时创建,一般在栈上。
含有static关键词修饰的变量根据有无初始化,存储于数据段,即data段和bss段
不少公司面试喜欢问内存中堆和栈区别,以及内存模型等等。这里笔者发现了一个略有trick又不失区分度的题目:请写一段代码,用来指明程序中堆栈段的大致位置。
后续会公布答案,答案非常简单也很神奇,请大家积极思考或者留言~
以上就是笔者对于堆栈以及内存模型的一些理解,在总结过程中参考不少资料,以确保可靠性。希望能够解答大家的疑惑。
感谢阅读,欢迎分享、关注、点赞~
INFOCOM诚可贵,NSDI价更高,若为SIGCOMM故,二者皆可抛。
当被问到这个问题的时候我首先问了一下面试官,这个堆栈指的是内存还是数据结构。 万万没想到的是面试官说你都分别介绍一下吧。 数据结构: 栈:一种先进后出的数据结构,有push,pop,top等函数 堆:用二叉树形式组织的数据结构,分为最大堆和最小堆。 内存:摘自《面试宝典》 ...
2016年国庆假期终于把此书过完,整理笔记和体会于此。 关于书名 书名源于俄罗斯的演员斯坦尼斯拉夫斯基创作的《演员的自我修养》,作者为了写这本书前前后后修改了三十年之久,临终前才同意不在修改,拿去出版。使用这个书名一方面书单内容的确不是介绍一门新的编程语言或是展示一些实用的...
C语言中内存分配 在任何程序设计环境及语言中,内存管理都十分重要。在目前的计算机系统或嵌入式系统中,内存资源仍然是有限的。因此在程序设计中,有效地管理内存资源是程序员首先考虑的问题。 第1节主要介绍内存管理基本概念,重点介绍C程序中内存的分配,以及C语言编译后的可执行程序的...
从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗offer。我找的是java后台开发,把常见的问题分享给大家,有一些是自己的总结,有一些是网上借鉴的内容。希望能帮助到各位。预祝各位同学拿到自己心仪的offer...
这篇文章是我整合了网友们的文章,具体来源(哪位大牛写的我也不清楚)不过在此膜拜一下,学过C/C++的看一下或许有帮助。如果只是想了解一下请看上一篇 栈与堆 (一) 六、底层介绍堆(heap)和栈(stack)是C/C++编程不可避免会碰到的两个基本概念。首先,这两个概念都可...
依稀记得来大学报到的第一天,一个人拖着笨重的行李箱,不会搭地铁,搭公交不知道要往哪个方向去,来到学校已是下午四点左右,工作人员已基本下班,跟着一个师姐迷迷糊糊的交了学费,注册登记好后已没有了宿舍,师姐帮忙去找宿舍,自己就傻傻的在等着,看着天渐渐黑下来,一种莫名的无助感油然而...
20:15 打开App
从上周开始,妈妈就问我说,你要不要去学画画的,我想了想说:“好啊!”然后妈妈又说:“我的同事她的女儿也想学画画,要不你们两个一块儿吧!”当妈妈说到是同事的女儿的时候,我就想问妈妈,这个人是不是王筱冉呢?我说出来了,妈妈惊讶的问...
一、注解是用来干嘛的? 便于生成文档。用于编译时的检查。用于简洁化代码。 首先,生成文档这个最常见,如果你看过一些android源码就会发现 像里面的@link,@param,@see等等这些,主要为了方便用户阅读。 其次,编译时检查,例如: 我们知道@Override代表...
一、 “老板,上酒!”嘈杂的酒馆里传来一声呼喝,崔莺精神一震,提起嗓音应了一声,端着一坛竹叶青朝最中间的方桌走去。 那客人锦衣华服,身侧两名美姬如水蛇般在他身上扭动着躯体,客人肥胖的手掌在她们光滑白皙的胴体上尽情游走,毫不顾忌的大笑。 场面香艳至极,送过酒后,崔莺却不敢多看...

我要回帖

更多关于 16进制转换10进制 的文章

 

随机推荐