参考链接:difference between stack and heap
目录
内存管理
堆和栈的区别
Stack
Heap
内存管理
对于一个进程的内存空间而言,可以在逻辑上分成3个部份:代码区,静态数据区和动态数据区。动态数据区一般就是“堆栈”。
C++中,内存分为5个区:堆、栈、自由存储区、全局/静态存储区和常量存储区。
栈:是由编译器在需要时自动分配,不需要时自动清除的变量存储区。通常存放局部变量、函数参数等。
堆:是由new分配的内存块,由程序员释放(编译器不管),一般一个new与一个delete对应,一个new[]与一个delete[]对应。如果程序员没有释放掉, 资源将由操作系统在程序结束后自动回收。
自由存储区:是由malloc等分配的内存块,和堆十分相似,用free来释放。
全局/静态存储区:全局变量和静态变量被分配到同一块内存中(在C语言中,全局变量又分为初始化的和未初始化的,C++中没有这一区分)。
常量存储区:这是一块特殊存储区,里边存放常量,不允许修改。
//main.cpp
int a = 0; //全局初始化区
char *p1; //全局未初始化区
main(){int b; //栈
char s[] = "abc"; // 栈
char *p2; //栈
char *p3 = "123456"; // 123456/0在常量区,p3在栈上。
static int c = 0;// 全局(静态)初始化区
p1 = (char *)malloc(10)
p2 = (char *)malloc(20) // 分配得来得10和20字节的区域就在堆区。
strcpy(p1, "123456"); // 123456/0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
}
├———————┤低端内存区域 │ …… │ ├———————┤ │ 动态数据区 │ ├———————┤ │ …… │ ├———————┤ │ 代码区 │ ├———————┤ │ 静态数据区 │ ├———————┤ │ …… │ ├———————┤高端内存区域
堆和栈的区别
Stack
- stack involves linear and sequential allocation of the memory;
- LIFO先进后出,具有2个指针分别指向first entry和last entry。
- fucntion calls。一个function call可以hold一些stack enrty的集合,也叫做stack frame。当function call 发生时,stack frame会被push进stack。(进栈)
- 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
- allocation 和deallocation is finshed by CPU
- 申请时:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
- 大小限制:1M OR 2M?
- 生长方向:向高地址
- 栈有静态分配和动态分配,静态分配由编译器完成(如局部变量分配),动态分配由alloca函数 分 配,但栈的动态分配的资源由编译器进行释放,无需程序员实现。
- 如果function调用结束,对应的stack frame will be gone
Heap
- heaps act as a pool of storage are that allocated the memory randomly (Dynamic)
- 一个分配请求会被返回一个pointer to the allocated memory。
- deallocation需要程序员手动完成
- A function call in stack takes O(N) time. In contrast, it takes O(1) time in a heap.
- 申请时:操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
- 堆大小受限于计算机系统中有效的虚拟内存(32bit 系统理论上是4G),由此可见,堆获得的空间比较灵活,也比较大。
- 生长方向:向低地址
- 堆都是动态分配
- 可以在thread之间共享
- stack involves linear and sequential allocation of the memory;
- LIFO先进后出,具有2个指针分别指向first entry和last entry。
- fucntion calls。一个function call可以hold一些stack enrty的集合,也叫做stack frame。当function call 发生时,stack frame会被push进stack。(进栈)
- 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
- allocation 和deallocation is finshed by CPU
- 申请时:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
- 大小限制:1M OR 2M?
- 生长方向:向高地址
- 栈有静态分配和动态分配,静态分配由编译器完成(如局部变量分配),动态分配由alloca函数 分 配,但栈的动态分配的资源由编译器进行释放,无需程序员实现。
- 如果function调用结束,对应的stack frame will be gone
Heap
- heaps act as a pool of storage are that allocated the memory randomly (Dynamic)
- 一个分配请求会被返回一个pointer to the allocated memory。
- deallocation需要程序员手动完成
- A function call in stack takes O(N) time. In contrast, it takes O(1) time in a heap.
- 申请时:操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
- 堆大小受限于计算机系统中有效的虚拟内存(32bit 系统理论上是4G),由此可见,堆获得的空间比较灵活,也比较大。
- 生长方向:向低地址
- 堆都是动态分配
- 可以在thread之间共享
不同类型数据的存储地址:
#includeint g1=0, g2=0, g3=0; int main() { static int s1=0, s2=0, s3=0; int v1=0, v2=0, v3=0; //打印出各个变量的内存地址 printf("0x%08xn",&v1); //打印各本地变量的内存地址 printf("0x%08xn",&v2); printf("0x%08xnn",&v3); printf("0x%08xn",&g1); //打印各全局变量的内存地址 printf("0x%08xn",&g2); printf("0x%08xnn",&g3); printf("0x%08xn",&s1); //打印各静态变量的内存地址 printf("0x%08xn",&s2); printf("0x%08xnn",&s3); return 0; }
结果:
0x005ffd74 0x005ffd68 0x005ffd5c 0x002d070c 0x002d0710 0x002d0714 0x002d0718 0x002d071c 0x002d0720
全局变量和静态变量分配的内存是连续的。这是因为本地变量和全局/静态变量是分配在不同类型的内存区域中的结果。
函数调用
#includevoid __stdcall func(int param1,int param2,int param3) { int var1=param1; int var2=param2; int var3=param3; printf("0x%08xn",param1); //打印出各个变量的内存地址 printf("0x%08xn",param2); printf("0x%08xnn",param3); printf("0x%08xn",&var1); printf("0x%08xn",&var2); printf("0x%08xnn",&var3); return; } int main() { func(1,2,3); return 0; }
0x0012ff78 0x0012ff7c 0x0012ff80 0x0012ff68 0x0012ff6c 0x0012ff70
├———————┤<—函数执行时的栈顶TOP(ESP)、低端内存区域 │ …… │ ├———————┤ │ var 1 │ ├———————┤ │ var 2 │ ├———————┤ │ var 3 │ ├———————┤ │ RET │ ├———————┤<—“__cdecl”函数返回后的栈顶(ESP) │ parameter 1 │ ├———————┤ │ parameter 2 │ ├———————┤ │ parameter 3 │ ├———————┤<—“__stdcall”函数返回后的栈顶(ESP) │ …… │ ├———————┤<—栈底BOTTOM(基地址 EBP)、高端内存区域
参考:
函数调用过程:3个参数按照从右到左的顺序压入栈,然后param3 param2 param1,然后压入函数的返回地址(RET)。
e.g.堆栈上的变量
int x = 1; int y = 1; int* hx = new int(1); cout << "address of x:" << &x <结果为:
address of x:004FFC04 address of y:004FFBF8 address of hx:004FFBEC hx:00961F90 *hx:1



