C++ 内存管理
静态区域和动态区域两个部分,静态区域主要用于存储程序中的代码部分、常量、全局的变量以及静态变量(全局+局部),而动态区域主要是系统或者程序员进行动态进行的分配的内存,是在程序运行中进行分配的。
1 静态区域
代码段(text segment): 包括只读存储区以及文本区,其中只读存储区存储的是字符串常量,文本区存储的是机器代码,比如一些可执行指令。
数据段(data segment): 用于存放程序中 已经初始化 的全局变量和静态变量
BSS段: 存储 未初始化 的全局变量和静态变量(局部+全局),以及所有被初始化为0的全局变量和静态变量(对于未初始化的全局变量和静态变量,程序运行main之前时会统一清零),即未初始化的全局变量编译器会初始化为0。
2 动态区域
堆(heap): 堆得大小并不固定,可以动态的扩张或者缩减。进行分配时是由malloc()以及new()这一类实时内存分配函数来实现的。刚开始,当进程未调用malloc()以及new()这一类实时内存分配函数时是没有堆段的,在进行调用这些实时分配函数之后分配一个堆段,并在程序运行过程中,可以动态的增加堆得大小,是由低地址向高地址增长的。
栈(stack): 用来存储函数调用时的临时信息,比如函数调用所传递的参数、函数的返回地址、函数的局部变量等等。在程序运行时,是由编译器在需要的时候进行分配,在不需要的时候自动清除。栈内存的申请和释放都按照先进后出(LIFO)。
C/C++内存布局如下图所示:
3 区分堆和栈
如何进行区分堆和栈,我们通过一个小例子来进行解释:
void f(){
int* p=new int[5];
.......
}
上面的代码中就包括了栈和堆,new 语句显示的是分配了一块堆内存,对于指针变量p则是指栈内存中存放了一个指向堆内存的指针p。在程序中会先确定在堆中分配内存的大小,然后调用operator new进行分配内存,然后返回内存的首地址,放进栈中。同时我们还应该回收所分配的内存,使用delete []p,是为了告诉编译器,我们删除的是一个数组。
栈内存: 由 程序 自动向操作系统申请分配以及回收,速度快,使用方便,但程序员无法控制。若分配失败,则提示栈溢出错误。注意,const局部变量也储存在栈区内,栈区向地址减小的方向增长。
堆内存: 程序员 向操作系统申请一块内存,当系统收到程序的申请时,会遍历一个记录空闲内存地址的链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。分配的速度较慢,地址不连续,容易碎片化。此外,由程序员申请,同时也必须由程序员负责销毁,否则则导致内存泄露。
关于堆和栈区别的比喻:
使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。
使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。
堆和栈的区别
1. 管理方式不同:
- 对于栈来讲,是由编译器自动管理,无需我们手工控制;
- 对于堆来说,申请和释放工作由程序员控制,容易产生memory leak。
2. 空间大小不同:
- 对于栈来讲,一般都是有一定的空间大小的,一般默认的栈空间大小为1M,同时,我们是可以进行修改的;
- 对于堆来说,在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。
3. 能否产生碎片不同:
- 对于栈来讲,不会有碎片,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出(具体操作和数据结构中的栈操作是一致的);
- 对于堆来说,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。
4. 生长方向不同:
- 对于栈来讲,生长方向是向下的,是向着内存地址减小的方向增长;(参考C/C++内存布局图)
- 对于堆来说,生长方向是向上的,也就是向着内存地址增加的方向。
5. 分配方式不同:
- 对于栈来讲,栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。;(参考C/C++内存布局图)
- 对于堆来说,堆都是动态分配的,没有静态分配的堆。
6. 分配效率不同:
- 对于栈来讲,栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。
- 对于堆来说,堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。
从这里我们可以看到,堆和栈相比,由于大量new/delete的使用,容易造成大量的内存碎片;由于没有专门的系统支持,效率很低;由于可能引发用户态和核心态的切换,内存的申请,代价变得更加昂贵。所以栈在程序中是应用最广泛的,就算是函数的调用也利用栈去完成,函数调用过程中的参数,返回地址,EBP和局部变量都采用栈的方式存放。所以,我们推荐大家尽量用栈,而不是用堆。
参考文献
1 https://blog.csdn.net/caogenwangbaoqiang/article/details/79788368
2 https://zhuanlan.zhihu.com/p/51855842
3 https://www.cnblogs.com/ChenZhongzhou/p/5685537.html