内存中的堆栈
函数调用入栈顺序
在函数调用时,第一个进栈的是主函数中函数调用后的下一条指令的地址,然后是函数的各个参数。再然后是函数中的局部变量。注意静态变量是不入栈的。
Pascal语言中函数参数从左到右入栈的,C语言则从右至左。原因是Pascal语言不支持可变长参数,而C语言支持这种特色。通过栈堆分析可知,自左向右的入栈方式,最前面的参数被压在栈底。除非知道参数个数,否则是无法通过栈指针的相对位移求得最左边的参数。
堆栈溢出
堆栈溢出的产生是由于过多的函数调用,导致调用堆栈无法容纳这些调用的返回地址。堆栈溢出很可能由无限递归(Infinite recursion)产生
#include<stdio.h> int main() { char name[8]; printf("Please type your name:"); gets(name); printf("Hello.%s!",name); return 0; }
编译并且执行,输入ipxodiAAAAAAAAAAAAAAAA,执行完gets(name)之后,堆栈如下:
由于我们输入的name字符串太长,name数组容纳不下,只好向内存顶部继续写'A',如果提前申请动态内存就可以避免堆栈溢出。而
堆栈既可以向下增长(向内存低地址)也可以向上增长, 这依赖于具体的实现。 在我们的例子中, 堆栈是向下增长的。 这是很多计算机的实现方式, 包括Intel, Motorola, SPARC和MIPS处理器。由于堆栈的生长方向与内存的生长方向相反,这些'A’覆盖了堆栈的 老的元素。'EBP ret’都被'A'覆盖了。在main返回的时候,就会把'AAAA'的ASCII码:0x41414141作为返回地址,CPU会试图执行 0x41414141处的指令,结果出现错误。这就是一次堆栈溢出!
堆栈和线程的关系
堆: 是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。
栈:是线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立。每个C++对象的数据成员也存在在栈中,每个函数都有自己的栈,栈被用来在函数之间传递参数。操作系统在切换线程的时候会自动的切换栈,就是切换SS/ESP寄存器。栈空间不需要在高级语言里面显式的分配和释放。
栈:是线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立。每个C++对象的数据成员也存在在栈中,每个函数都有自己的栈,栈被用来在函数之间传递参数。操作系统在切换线程的时候会自动的切换栈,就是切换SS/ESP寄存器。栈空间不需要在高级语言里面显式的分配和释放。