Fork me on GitHub

C++内存管理

总结了一部分内存管理的知识

在c++中内存主要分为5个存储区:

栈(Stack):局部变量,函数参数等存储在该区,由编译器自动分配和释放.栈属于计算机系统的数据结构,进栈出栈有相应的计算机指令支持,而且分配专门的寄存器存储栈的地址,效率分高,内存空间是连续的,但栈的内存空间有限。
堆(Heap):需要程序员手动分配和释放(new,delete),属于动态分配方式。内存空间几乎没有限制,内存空间不连续,因此会产生内存碎片。操作系统有一个记录空间内存的链表,当收到内存申请时遍历链表,找到第一个空间大于申请空间的堆节点,将该节点分配给程序,并将该节点从链表中删除。一般,系统会在该内存空间的首地址处记录本次分配的内存大小,用于delete释放该内存空间。
全局/静态存储区:全局变量,静态变量分配到该区,到程序结束时自动释放,包括DATA段(全局初始化区)与BBS段(全局未初始化段)。其中,初始化的全局变量和静态变量存放在DATA段,未初始化的全局变量和静态变量存放在BBS段。BBS段特点:在程序执行前BBS段自动清零,所以未初始化的全局变量和静态变量在程序执行前已经成为0.
文字常量区:存放常量,而且不允许修改。程序结束后由系统释放。
程序代码区:存放程序的二进制代码
使用存储区的三种方式:
1)静态存储区(Static Memory)
全局变量,静态变量及静态类成员存储在该区,在编译期间就进行分配,生存期到程序结束。存储在该区的对象只初始化一次,且在程序运行期间地址固定不变。
2)自动存储区(Autormatic Memory)
局部变量,函数参数等存储在该区,由编译器自动分配和释放
3)自由存储区(Free Store)
由程序员手动分配和释放内存(new,delete)

堆和栈的区别:

1)空间大小:栈的内存空间是连续的,空间大小通常是系统预先规定好的,即栈顶地址和最大空间是确定的;而堆得内存空间是不连续的,由一个记录空间空间的链表负责管理,因此内存空间几乎没有限制,在32位系统下,内存空间大小可达到4G
2)管理方式:栈由编译器自动分配和释放,而堆需要程序员来手动分配和释放,若忘记delete,容易产生内存泄漏。
3)生长方向不同:对于栈,他是向着内存地址减小的方向生长的,这也是为什么栈的内存空间是有限的;而堆是向着内存地址增大的方向生长的
4)碎片问题:由于栈的内存空间是连续的,先进后出的方式保证不会产生零碎的空间;而堆分配方式是每次在空闲链表中遍历到第一个大于申请空间的节点,每次分配的空间大小一般不会正好等于申请的内存大小,频繁的new操作势必会产生大量的空间碎片
5)分配效率:栈属于机器系统提供的数据结构,计算机会在底层对栈提供支持,出栈进栈由专门的指令执行,因此效率较高。而堆是c/c++函数库提供的,当申请空间时需要按照一定的算法搜索足够大小的内存空间,当没有足够的空间时,还需要额外的处理,因此效率较低。

  • 使用内存时几点注意事项:
    1)用new和malloc申请内存时,在使用前要检查内存是否分配成功
    char p=new char[10];
    if(p==NULL)
    return;
    2)使用内存之前要进行初始化
    3)在对内存进行操作时,防止越界,如数组操作要注意下标范围
    4)对于动态分配的内存,一定要手动释放,否则程序每运行一次就会丢失一部分内存,造成内存泄漏
    5)防止内存释放后继续使用它,主要有以下三种情况:
    a.程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。
    b.函数的return语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。
    c.使用free或delete释放了内存后,没有将指针设置为NULL。导致产生“野指针”。
    野指针:“野指针”不是NULL指针,是指向“垃圾”内存的指针。人们一般不会错用NULL指针,因为用if语句很容易判断。但是“野指针”是很危险的,if语句对它不起作用。
    野指针”的成因主要有三种:
    (a)指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。
    char
    p; //此时p为野指针
    (b)指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针.
    char p=new char[10]; //指向堆中分配的内存首地址
    cin>> p;
    delete []p; //p重新变为野指针
    (c)指针操作超越了变量的作用范围。
    char
    p=new char[10]; //指向堆中分配的内存首地址
    cin>> p;
    cout<< (p+10); //可能输出未知数据
    6)指针的注意点:
    a.指针指向常量存储区对象
    char
    p=”abc”;
    此时p指向的是一个字符串常量,不能对p的内容进行写操作,如srtcpy(p,s)是错误的,因为p的内容为“abc”字符串常量,该数据存储在常量存储区,但可以对指针p进行操作,让其指向其他的内存空间。
    b.资源泄漏
    char
    p=new char[3]; //分配三个字符空间,p指向该内存空间
    p=”ab”; //此时p指向常量“ab”,而不再是new char分配的内存空间了,从而造成了资源泄漏
    delete []p; //释放时报错
    c.内存越界
    char * p=new char[3]; //分配三个字符空间,p指向该内存空间
    strcpy(p,”abcd”); //将abcd存处在分配的内存空间中,由于strlen(“abcd”)=4>3,越界
    delete []p; //释放时出错

    malloc calloc realloc 的区别

    区别:
    (1)函数malloc不能初始化所分配的内存空间,而函数calloc能.如果由malloc()函数分配的内存空间原来没有被使用过,则其中的每一位可能都是0;反之, 如果这部分内存曾经被分配过,则其中可能遗留有各种各样的数据.也就是说,使用malloc()函数的程序开始时(内存空间还没有被重新分配)能正常进行,但经过一段时间(内存空间还已经被重新分配)可能会出现问题.
    (2)函数calloc() 会将所分配的内存空间中的每一位都初始化为零,也就是说,如果你是为字符类型或整数类型的元素分配内存,那么这些元素将保证会被初始化为0;如果你是为指针类型的元素分配内存,那么这些元素通常会被初始化为空指针;如果你为实型数据分配内存,则这些元素会被初始化为浮点型的零.
    (3)函数malloc向系统申请分配指定size个字节的内存空间.返回类型是 void类型.void表示未确定类型的指针.C,C++规定,void* 类型可以强制转换为任何其它类型的指针.
    (4)realloc可以对给定的指针所指的空间进行扩大或者缩小,无论是扩张或是缩小,原有内存的中内容将保持不变.当然,对于缩小,则被缩小的那一部分的内容会丢失.realloc并不保证调整后的内存空间和原来的内存空间保持同一内存地址.相反,realloc返回的指针很可能指向一个新的地址.
    (5)realloc是从堆上分配内存的.当扩大一块内存空间时,realloc()试图直接从堆上现存的数据后面的那些字节中获得附加的字节,如果能够满足,自然天下太平;如果数据后面的字节不够,问题就出来了,那么就使用堆上第一个有足够大小的自由块,现存的数据然后就被拷贝至新的位置,而老块则放回到堆上.这句话传递的一个重要的信息就是数据可能被移动.
    C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。

    在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会。

operator new与operator delete函数

new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。
联系
一般来说
operator new = malloc+构造函数(若是自定义类型)+失败抛异常
operator delete = free+析构函数(若是自定义类型)+失败抛异常
注意:operator new和operator delete用户也可以自己实现,用户实现时即可实现成全局函数,也可实现成类的成员函数,但是一般情况下不需要实现,除非有特殊需求。

new与malloc,delete与free的区别

内置类型

如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。

自定义类型

new的原理
1.调用operator new函数申请空间
2.在申请的空间上执行构造函数,完成对象的构造
delete的原理
1 在空间上执行析构函数,完成对象中资源的清理工作
2.调用operator delete函数释放对象的空间
new T[N]的原理
1.调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申

2.在申请的空间上执行N次构造函数
delete[]的原理
1.在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
2.调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间
malloc与free
正常的申请释放,无明显差别

其他区别

malloc/free和new/delete的区别
malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:

  1. malloc和free是函数,new和delete是操作符
  2. malloc申请的空间不能初始化,new可以初始化
  3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可
  4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
  5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
  6. malloc/free只能申请内置类型的空间,不能申请自定义类型的空间,因为其不会调用构造与析构函数,
    而new可以,new在申请空间后会调用构造函数完成对象的构造,delete在释放空间前会调用析构函数
    完成空间中资源的清理
  7. malloc申请的空间一定在堆上,new不一定,因为operator new函数可以重新实现
  8. new/delete比malloc和free的效率稍微低点,因为new/delete的底层封装了malloc/free
-------------本文结束感谢您的阅读-------------
坚持原创技术分享,您的支持将鼓励我继续创作!