C++-内存管理

参考博客:https://blog.csdn.net/jing0611/article/details/4030237

在最开始的学习中,老师一直在说一个关于C++的话题。C++是一个极度追求性能的语言。
通过学习,这不是开玩笑,在我所学习的语言中,对内存的使用有如此的执着的“较真”也就是C++了。

因为性能的缘故,一个内存不合理的分配,都可能在日后成为一个隐患,成为一个令人头疼的BUG。所以最近想好好总结一下内存管理。我们在学习中也经常听到栈溢出,内存泄漏等这些感觉很严重的问题。首先我们要搞清楚什么是栈和什么是堆。

栈和堆

首先要理解一个关键的问题。我们此时说的是内存分配中的栈和堆,不是在讨论数据结构中的栈和二叉堆。我在之前学习操作系统的时候划过一个图,是关于Linux下操作系统程序地址空间的。中间还有栈区和堆区的分布

内存管理

从图中可以看出基本分布,内存中的栈区是处于高地址以地址的增长方向为上,栈地址是向下增长的。从上往下,栈区是分配局部变量空间。堆区是从下往上,堆区的地址是向上增长的用于分配程序员申请的内存空间。

内存分配方式

内存分配方式有三种

从静态存储区域分配

内存在程序编译的时候就已经分配好了,这块内存在程序的整个运行期间都存在

例如全局变量,static静态成员变量

在栈上创建

执行函数时,函数内部变量的存储单位可以在栈上创建,函数执行结束时,这些存储单元自动释放。栈内存分配运算置于处理器的指令集中,效率很高,但是分配的内存容量有限

在堆上分配

也称为动态内存分配。程序在运行的时候用malloc或new申请任意多少内存,程序员自己负责在何时用free或delete来释放这块内存。动态内存的生命周期由程序员决定,使用非常灵活,但如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现泄漏,频繁地分配和释放不同大小的堆空间将会产生堆内碎块。也就是我们常说的内存碎片。

程序内存空间

一个程序将操作系统分配给其运行的内存分为4个区域

代码区(code area)
全局数据区(data area)
堆区(heap area)
栈区(stack area)

栈区:由编译器自动分配释放,存放为函数运行的局部变量,函数参数,返回数据,返回地址等。操作方式与数据结构中的类似。

堆区:一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。分配方式类似于链表

全局数据区:也叫做静态区,存放全局变量,静态数据。程序结束后由系统释放

文字常量区:可以理解为常量区,常量字符串存放这里。程序结束后由系统释放

程序代码区:存放函数体的二进制代码。但是代码段中也分为代码段和数据段。

关于文字常量区

文字常量区,在大多数解释中,都仅仅说明常量字符串存放这里。但是如果深究字眼,那么其他常量比如整型是否存放这里呢?我查阅了一些资料,是这么解释的:常量之所以称为“文字常量”,其中“文字”是指我们只能以它的值的形式指代它,“常量”是指它的值是不可变的。同时注意一点:文字常量是不可寻址的(即我们的程序中不可能出现获取所谓常量20的存储地址&20这样的表达式),虽然常量也是存储在内存的某个地方,但是我们没有办法访问常量的地址的。

还有就是我们都知道的常量是有类型的。所以总的来说,只要是常量都存放在文字常量区!!

程序例子
int a = 0; //全局初始化区
char *p1; //全局未初始化区
int main() {
int b; //栈
char s[] = /"abc/"; //栈
char *p2; //栈
char *p3 = /"123456/"; //123456//0在常量区,p3在栈上。
static int c =0;//全局(静态)初始化区
p1 = new char[10];
p2 = new char[20];
//分配得来得和字节的区域就在堆区。
strcpy(p1, /"123456/"); //123456//0放在常量区,编译器可能会将它与p3所指向的/"123456/"优化成一个地方。
}

堆与栈的区别

申请方式

stack:由系统自动分配。比如在函数运行中声明一个局部变量int b = 10;,系统自动在栈中为b开辟空间。

heap:需要程序员自己申请,并指明大小,在C中是有malloc函数,在C++中多使用new运算符

p1 = (char*)malloc(10);
p2 = new char[10];
此时的p1和p2是在栈中的

从C++角度上说,使用new分配堆空间可以调用类的构造函数,而malloc()函数仅仅是一个函数调用,它不会调用构造函数,它所接受的参数是一个unsigned long类型。同样,delete在释放堆空间之前会调用析构函数,而free函数则不会。

申请后系统的响应

stack:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出

heap:在操作系统中有一个记录空闲内存地址的表,这是一种链式结构。它记录了有哪些还未使用的内存空间。当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。

对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。由于找到的堆结点的大小不一定正好等于申请的大小,系统会将自动的多余的那部分重新放入空闲链表中。

申请大小的限制

stack:在WINDOWS中,栈是向下增长的,是一块连续的区域。也就是说栈从栈顶的地址和栈的最大容量是系统预先规定好的。栈的大小是2M。当超过这个内存之后,会报Overflow异常。所以栈能申请的空间较小。

heap:堆是向上增长的,向高地址递增的,因为它是一个用链表来存储的空闲地址空间。自然是个不连续的内存区域。堆的大小受限于计算机系统中有效的虚拟内存。我们知道当我们玩网络游戏时,当虚拟内存不够时我们会更改虚拟内存的大小。一般初始值为512M,可以更大,所以堆能分配的区域是相当大的。

申请效率的比较

stack:由系统自动分配,速度较快,但是程序员不可控

heap:由new或者malloc申请的一块新空间(内存),一般速度比较慢,而且容易产生内存碎片,不过用起来方便。

在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是栈,而是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活。

VirtualAlloc是一个Windows API函数,该函数的功能是在调用进程的虚拟地址空间,预定或者提交一部分页。

堆和栈的存储内容

stack:在函数调用中,第一个进栈的是主函数中后的第一个指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右向左入栈的,然后是函数中的局部变量。但是,静态变量不入栈

当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。

heap:一般是在堆的头部用一个字节存放堆的大小。堆中的内容由程序员自己安排。

存取效率的比较

char s1[] = 'a';
char *s2 = 'b';

a是在运行时刻才赋值的;而b是在编译时就确定的;但是,在以后的存取中,在栈上的数组比指针所指向的字符串(堆)快。

无论是堆还是栈,都要防止越界现象的发生(除非你是故意使其越界),因为越界的结果要么是程序崩溃,要么是摧毁程序的堆、栈结构,产生以想不到的结果。

个人总结

堆和栈的分配区别在一下几个方面

1、管理方式不同;

2、空间大小不同;

3、能否产生碎片不同;

4、生长方向不同;

5、分配方式不同;

6、分配效率不同;

管理方式: 栈是在函数运行时,由系统自动分配;而堆是通过程序员自己调用malloc函数或者new运算符去申请一个需要的大小空间。

空间大小: 栈的空间大小并不大,一般最多为2M,超过之后会报Overflow错误。堆的空间非常大,最大可到达4G,可操作的空间非常大。

能否产生碎片: 栈的操作与数据结构中的栈用法是类似的。‘后进先出’的原则,以至于不可能有一个空的内存块从栈被弹出。因为在它弹出之前,在它上面的后进栈的数据已经被弹出。它是严格按照栈的规则来执行。但是堆是通过new/malloc随机申请的空间,频繁的调用它们,则会产生大量的内存碎片。这是不可避免地。

生长方向: 栈的生长方向是由高地址向低地址增长,是自上而下的。堆的生长方向是由低地址向高地址增长,是自下而上的。

分配方式: 堆都是动态分配的,没有静态分配。但是栈有两种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由malloc函数实现,但是栈的动态分配和堆是不同的,它的动态分配是由编译器进行和释放,无需程序员进行操作。

分配效率: 栈是机器系统提供的数据结构,计算机底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行。这就决定了栈有着很高的效率。堆需要通过C/C++的库函数进行一个复杂的算法,在对内存中搜寻一个足够大小的空间,如果没有足够的空间(内存碎片空间太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低的多。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章