打印

堆栈为什么会溢出

[复制链接]
2632|17
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
crazy2012|  楼主 | 2013-10-13 22:31 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
沙发
aozima| | 2013-10-14 09:20 | 只看该作者
本帖最后由 aozima 于 2013-10-14 09:23 编辑

栈溢出
int main()
{
    char buffer[1024*1024*128];  //仅适用于单片机环境
    for(i=0; i<sizeof(buffer); i++)
    {
        buffer[i] = i
    }

    return 0;
}
堆溢出
int main()
{
    char * p = malloc(100);
    for(i=0; i<200; i++)
    {
        p[i] = i
    }

    return 0;
}

使用特权

评论回复
板凳
江枫渔火| | 2013-10-14 09:58 | 只看该作者
常言所说的堆栈溢出,就是指栈溢出。
使用 malloc(); 函数是动态分配内存堆区的空间,一般的程序如没用这个,就不存在堆溢出。
栈溢出:
   栈空间是预设的,它通常用于存放临时变量,如果你在函数内部定义一个局部变量,空间超出了设置的栈空间大小,就会溢出。不仅如此,如果函数嵌套太多,也会发生栈溢出,因为函数没有结束前,函数占用的变量也不被释放,占用了栈空间。
解决办法:恰当的设置栈空间大小。分析代码,评估在哪个环节,使用的临时变量字节数最大,所设置的栈空间必须大于此。也可以将大的变量在全局进行定义,它就不占用栈区空间。


使用特权

评论回复
地板
南宫云明| | 2013-10-14 10:20 | 只看该作者
江枫渔火 发表于 2013-10-14 09:58
常言所说的堆栈溢出,就是指栈溢出。
使用 malloc(); 函数是动态分配内存堆区的空间,一般的程序如没用这个 ...

路过的菜鸟问一下。。。数组越界是不是算堆溢出?

像我们平时用keil,一般不会注意去分配堆栈空间,是不是由编译器来配置默认的堆空间和栈空间,如果我们需要自己来分配堆空间和栈空间,是不是在单片机一开始使用汇编来配置?

使用特权

评论回复
5
江枫渔火| | 2013-10-14 11:42 | 只看该作者
南宫云明 发表于 2013-10-14 10:20
路过的菜鸟问一下。。。数组越界是不是算堆溢出?

像我们平时用keil,一般不会注意去分配堆栈空间,是不 ...

数组越界 跟 栈溢出 是两个不同概念

数组越界:首先这是说明已经定义了一个数组,你在引用这个数组的单元的时候,超出了这个数组元素的个数。
比如你定义了一个具有10个单元的数组 a[10],你使用a[100]=10,就是数组的范围超出原来定义的了。
好比你拿了10个杯子装了10杯酒,但是你要拿那第11杯酒,就根本不存在,这就是错误。

栈溢出:首先说明,在定义(分配)这个局部变量的时候,就已经不成功了,执行这个操作的时候,系统发现已经没有足够的空间给你定义局部变量了。也就是你在定义数组的时候已经出问题了,至于你越不越界都不重要了。

平时用KEIL是不太关心堆栈分配,是因为做的东西对资源要求不严,随随便便就能满足要求。但代码一大,资源就要紧张,一来考虑优化代码,二来也考虑调整内存分布,实际上堆栈的分配有两个含义,一个是起点在哪?二个是长度多少字节?在KEIL中有一部分内幕被封装掩盖了,但是使用默认的启动文件也可以找到蛛丝马迹。

keil新建工程的时候,选择完芯片,就会提示是否复制启动文件,选择是,可以参考这个启动文件(汇编的)修改栈设置。

使用特权

评论回复
评分
参与人数 1威望 +3 收起 理由
南宫云明 + 3 很给力!
6
江枫渔火| | 2013-10-14 11:52 | 只看该作者
数组越界在编译的时候会出错。你必须纠正才能编译出你的东西。
堆栈溢出在编译的时候不一定算是个错误,可能只是个警告,你可以编出个东西,而且烧录进去板子了,有时运行起来也觉得正常,但在运行到溢出的位置的时候,就会出现莫名其妙的问题的了。

使用特权

评论回复
7
南宫云明| | 2013-10-14 12:45 | 只看该作者
aozima 发表于 2013-10-14 09:20
栈溢出堆溢出

话说堆溢出我看好像就是数组越界吧。。。

使用特权

评论回复
8
z755924843| | 2013-10-14 13:17 | 只看该作者
江枫渔火 发表于 2013-10-14 11:42
数组越界 跟 栈溢出 是两个不同概念

数组越界:首先这是说明已经定义了一个数组,你在引用这个数组的单 ...

讲的很详细,受教了。

使用特权

评论回复
9
youluo235| | 2013-10-14 15:25 | 只看该作者
好比水满了会溢出

使用特权

评论回复
10
江枫渔火| | 2013-10-14 15:48 | 只看该作者
本帖最后由 江枫渔火 于 2013-10-14 16:01 编辑
南宫云明 发表于 2013-10-14 12:45
话说堆溢出我看好像就是数组越界吧。。。

你理解错误了,堆这种东西,其实是内存中剩余的空间。内存里,分配给了静态区(全局变量与静态局部变量),代码区,栈区,堆区。你所说的数组,通常都不是在堆区的东西。

如果你没有使用动态内存分配,那意味着,当你编译完你的程序后,你的代码有多少,占用了多少内存,以及每个符号在内存的哪个位置都是已经确定了。你没有用到的那部分内存空间,单片机运行一辈子都不会用到。对单片机程序中每一处将用到多少内存都是确定的。

数组若为全局变量,则是在静态区,数组若为局部变量,则是在栈区。可以这么理解:数组越界,仅仅是一小块变量的越界,而栈溢出,则是一个内存区域的越界。

越界与溢出有着类似的意思,但是需要知道参照为何物?越了什么界限?知道这一点你就分清楚了。

堆,是闲散内存,在代码里被动态分配,自然闲散内存的总空间也有限,使用堆的时候,就是申请一块连续的内存区域,并绑定到一个符号上,通常会是数组,或结构,或者结构数组,总之,堆的作用是存放大量数据的。用完之后要手动释放,不然堆的空间就越来越小,最后就无法动态分配内存~内存不足而爆机~

刚刚说到堆是内存的剩余空间,是说除了其他用到的确定的内存,用不到的内存都可以划到堆里面去。

堆栈溢出不代表内存不足,而是你用到的超出了预设,而你预设的可能还没有全部。

举个例子,就像你做预算一样,如果你有1000块钱,而你原计划零花500,实际你花的超出了,但是其实你有1000块的哟,你只要把你的预算调高一点,就可以花了。如果调到1000,你还是超了,那就真的是内存不足了。换个大点的。要不就省吃俭用,节省点?

使用特权

评论回复
11
南宫云明| | 2013-10-14 17:17 | 只看该作者
江枫渔火 发表于 2013-10-14 15:48
你理解错误了,堆这种东西,其实是内存中剩余的空间。内存里,分配给了静态区(全局变量与静态局部变量), ...

哦~~~说到最后还是有点模糊,我消化消化,不过还是很有帮助。。

使用特权

评论回复
12
wolension| | 2013-10-14 17:25 | 只看该作者
好像有很多台系的芯片都只有几级的硬件堆栈(hardware stack),而且没有软件堆栈,只要你调用嵌套多一点都会溢出.
记得PIC16C56只有两级堆栈,如果你在while(1)里调用按键程序,按键子程序里调用了按键扫描程序,在扫描程序里你调用了延时程序,很不幸,你溢出了,后果很严重,哈哈……

使用特权

评论回复
13
chenbb8| | 2013-10-14 17:46 | 只看该作者
wolension 发表于 2013-10-14 17:25
好像有很多台系的芯片都只有几级的硬件堆栈(hardware stack),而且没有软件堆栈,只要你调用嵌套多一点都 ...

栈不是只是SP自增加就行么,进入子函数之前保存PC然后保存上下文。
为什么要设计这样复杂无用的分层结构,是不是只是栈空间太小导致的假象。

使用特权

评论回复
14
江枫渔火| | 2013-10-14 23:24 | 只看该作者
没听说分级栈,只知道ARM有几种处理器模式,会有不同的栈,栈的方向有的是自增,有的是自减。

使用特权

评论回复
15
wolension| | 2013-10-15 08:40 | 只看该作者
chenbb8 发表于 2013-10-14 17:46
栈不是只是SP自增加就行么,进入子函数之前保存PC然后保存上下文。
为什么要设计这样复杂无用的分层结构 ...

有些芯片是SP用户是看不到的,也没有PUSH,POP之类的指类,你可以去看下PIC16C56的datasheet,你的PC和上下文保存到在哪里去,即使你自行保存了,那应该也是用goto而不是call,因为嵌套call超过两层肯定是溢出的。
程序分层在实际应用中很常见吧? 之前只是举个例子,难道你所有的功能都在主循环中实现而不写子程序?
我觉得程序嵌套很正常。

使用特权

评论回复
16
chenbb8| | 2013-10-15 09:52 | 只看该作者
wolension 发表于 2013-10-15 08:40
有些芯片是SP用户是看不到的,也没有PUSH,POP之类的指类,你可以去看下PIC16C56的datasheet,你的PC和上 ...

没用过这么怪的芯片,中断算不算是调用,如果在while(1)里调用了一个子程序,接着中断发生了,进入了ISR会不会溢出。

如果只是嵌套层次的问题的话,可以用参数宏或者inline函数来绕过去,不过这样子的话函数都要写在头文件里了。
解决的办法是将函数写到xxx_pkg.h中,然后被包含都普通的接口头文件中,再在接口头文件中声明函数,应该就可以 达到隐藏的目的了。不过这方法貌似 宏无法这样声明。

使用特权

评论回复
17
chenbb8| | 2013-10-15 09:54 | 只看该作者
前提是这个编译器支持inline,这么落后的架构在语言特性上估计也很落后~

使用特权

评论回复
18
chenbb8| | 2013-10-15 09:58 | 只看该作者
本帖最后由 chenbb8 于 2013-10-15 10:00 编辑

想了下参数宏的话,也许可以在pkg头文件中定义#define A_PKG(d) ……,然后再在接口头文件中定义一个接口宏

/***********
一堆一堆的说明
*************/
#define A(d) A_PKG(d)

这里的xxx_PKG.h可以用来代替C文件,定义宏的时候使用do{}while(0)这种结构,就可以在里面定义局部变量了。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

150

主题

939

帖子

9

粉丝