1.堆栈工作方式:
堆栈是一个特定的存储区或寄存器,它的一端是固定的,另一端是浮动的 ,也就是所有操作均在堆栈顶端进行,遵循“先进后出”的特征。
2.原理说明:
2.1:堆区栈区内存分配原则
·栈顶的地址和栈的最大容量是由系统预先规定的,只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常来提示栈发生溢出。
·堆区是由程序员自己申请,指明大小,程序最后进行释放,若程序员不释放,程序结束时可能由操作系统回收(注意,如果是C/C++语言,程序不进行对空间回收,而Java语言中有专门的垃圾回收器进行回收)。
2.2:溢出原理:
堆栈溢出是说堆区和栈区的溢出,二者同属于缓冲区溢出。从上面关于堆区和栈区的解释可以看出,一旦程序确定,堆栈内存空间的大小就是固定的,当数据已经把堆栈的空间占满时,再往里面存放数据就会超出容量,发生上溢;当堆栈中已经没有数据时,再取数据就无法取到了,发生下溢。
3.原因分析:
3.1:堆栈尺寸设置过小:
由堆栈溢出的定义便可知,堆栈尺寸设置过小时,其能储存的内容过小,容易发生溢出。
3.2:递归层次太深或函数调用层次过深导致堆栈溢出
函数递归调用时,系统要在栈中不断保存函数调用时的现场和产生的变量,如果递归调用太深,保存的调用现场和变量太多,当超过栈的空间长度时,即发生溢出就会造成栈溢出,这时递归无法返回。再有,当函数调用层次过深时也可能导致栈无法容纳这些调用的返回地址而造成栈溢出。
int A(int i)
{
if(递归终止条件)
{
return 0;//中止递归
}
else
{
for()
{
A();//使用循环进行递归
}
}
}
这样的递归结构在for循环次数过多,且递归中止条件一直无法满足时,栈内存就会产生溢出。
int B (int j);
int A(int i)
{
if(递归终止条件)
{
return 0;//中止递归
}
else
{
B();
}
}
int B(int j)
{
for()
{
A();
}
}
对比两种结构,一种是不断递归,第二种是将循环部分放到另一个函数中,当程序进入B()函数后,A()函数就已经结束了,它占用的栈内存就可以得到释放,所以不会产生过多的函数嵌套。
3.3:动态申请空间使用之后没有释放。
对于C语言,由于没有垃圾资源自动回收机制,因此,需要程序主动释放已经不再使用的动态地址空间,如果不释放,程序结束后该部分空间依然存在,还可以继续访问,也就是说这部分依然占据着堆空间,剩余的堆空间减少,就可能造成堆区溢出。
3.4:数组访问越界。
C语言没有提供数组下标越界检查,如果在程序中出现数组下标访问超出数组范围,在运行过程中可能会内存访问错误。
3.5:指针非法访问。
指针保存了一个非法的地址,通过这样的指针访问所指向的地址时会产生内存访问错误。
总结:
堆栈溢出其实可以细分为堆溢出和栈溢出,在通常情况下会有如下情况(对应了前面讲的原因中的前三点,后两点为内存访问错误的情况):
1.堆溢出:不断的new 一个对象,一直创建新的对象,
2.栈溢出:死循环或者是递归太深,递归的原因,可能太大,也可能没有终止。