尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

【C++】内存管理超详解——从C到C++的完整进化之路

【C++】内存管理超详解——从C到C++的完整进化之路 前言这篇博客我会从C语言的malloc/calloc/realloc讲起一路过渡到C的new/delete再深入到operator new、定位new这些底层机制。所有代码都是我自己亲手调试过的保证你看完就能上手。一、C/C内存分布在讲动态内存分配之前我们得先搞清楚一个C/C程序运行起来内存到底是怎么划分的先来看一段经典的面试题代码我把每一种变量都列出来了大家可以先自己想想它们分别存在哪里好公布答案之前我们先梳理一下C/C程序的内存区域划分从低地址到高地址依次是1. 代码段常量区存放可执行代码和只读常量比如字符串字面量abcd就存在这里2. 数据段静态区存放全局变量和静态变量程序结束才释放3. 堆动态内存分配的地方由程序员手动申请和释放向上增长4. 栈存放函数参数、局部变量等函数调用结束自动释放向下增长现在来回答上面的问题这可是面试选择题常客1.globalVar在哪里——数据段静态区因为是全局变量2.staticGlobalVar在哪里——数据段静态区static修饰的全局变量3.staticVar在哪里——数据段静态区注意static局部变量也是存在静态区的不是栈上4.localVar在哪里——栈普通局部变量5.num1在哪里——栈数组也是局部变量整个数组都在栈上6.char2在哪里——栈char数组是局部变量在栈上开辟空间然后把常量区的abcd拷贝过去7.*char2在哪里——栈解引用char2得到的是数组首元素a在栈上8.pChar3在哪里——栈指针变量本身是局部变量在栈上9.*pChar3在哪里——代码段常量区pChar3指向的是字符串常量abcd这个字符串在常量区10.ptr1在哪里——栈指针变量本身在栈上11.*ptr1在哪里——堆ptr1指向的是malloc出来的空间在堆上这里有个特别容易搞混的点char2和pChar3的区别。char2是数组在栈上开了5个字节含\0然后把常量区的abcd拷贝一份过去所以你可以修改char2的内容。而pChar3只是一个指针指向常量区的abcd常量区是只读的你要是试图修改*pChar3程序直接崩溃。还有一个关键点static修饰的局部变量虽然作用域只在函数内部但它的生命周期是整个程序因为它存在静态区不是栈上。所以static局部变量只会初始化一次下次调用函数时它还保留上次的值。二、C语言中的动态内存管理方式C语言中我们用malloc、calloc、realloc来申请堆内存用free来释放。2.1mallocint* ptr1(int*)malloc(sizeof(int*)*4)malloc最基本申请指定字节数的堆空间返回void*需要强转。申请到的空间是未初始化的里面是随机垃圾值。申请失败返回NULL所以用完一定要判空。2.2callocint* ptr2(int*)calloc(4,sizeof(int))calloc有两个参数第一个是元素个数第二个是每个元素的大小。它会把申请到的空间全部初始化为0。相当于malloc memset(0)的组合拳。2.3reallocint* ptr3(int*)realloc(ptr2,sizeof(int)*4)realloc是用来扩容的第一个参数是原内存地址第二个参数是新的总大小。它有两种扩容方式第一种原地扩容。如果原空间后面有足够大的连续空间直接在后面扩展返回原地址。第二种异地扩容。如果原空间后面不够大就找一块新的足够大的空间把原数据拷贝过去释放原空间返回新地址。所以realloc返回的地址可能和原来不一样一定要用新指针接收不能直接用原指针。还有一个面试常问的问题上面代码中realloc之后需要free(ptr2)吗答案是不需要因为realloc如果是异地扩容它内部会自动把旧空间释放掉。如果你手动再free一次就等于double free了程序直接崩。最后记得malloc/calloc/realloc 都要配对 free用完不free就是内存泄漏。三、C内存管理方式new和deleteC语言的内存管理方式在C中当然可以继续用但对于自定义类型malloc/free就显得力不从心了——它只负责开空间和释放空间不会调用构造函数和析构函数。所以C引入了new和delete这两个操作符专门用来管理动态内存。3.1new/delete操作内置类型先看内置类型的情况我写了四种常见用法这里有几个关键点第一new int 申请的单个int里面是随机值不会初始化。new int(10) 用圆括号就是初始化为10。第二new int[10] 申请的数组里面全是随机值。但如果用花括号列表初始化 new int[10]{1,2,3,4}那前四个元素是1、2、3、4剩下的六个编译器自动补0。这个特性C11之后才有的很方便。第三也是最重要的配对规则单个对象用 new释放用 delete数组对象用 new[]释放用 delete[]不能混用new出来的用delete[]释放或者new[]出来的用delete释放都是未定义行为可能导致内存泄漏甚至程序崩溃。3.2new/delete操作自定义类型new/delete和malloc/free最大的区别就体现在自定义类型上。我们定义一个A类来测试运行结果很明显malloc/free的那一组没有任何构造析构的打印。而new/delete的那两组new的时候打印构造delete的时候打印析构。总结一下对于自定义类型new 开空间 调用构造函数delete 调用析构函数 释放空间。malloc/free只负责开空间和释放空间不管构造析构。四、operator new与operator delete很多人以为new和delete是函数其实不对——new和delete是操作符。真正在底层负责申请和释放空间的是operator new和operator delete这两个全局函数。4.1operator newoperator new的底层其实还是调用malloc来申请空间。那它和malloc有什么区别呢区别在于失败处理malloc申请失败返回NULL而operator new申请失败会抛出bad_alloc异常。它的大致实现逻辑是这样的void* operator new(size_t size) { void* p; while ((p malloc(size)) 0) if (_callnewh(size) 0) { // 申请失败抛出bad_alloc异常 static const std::bad_alloc nomem; _RAISE(nomem); } return p; }简单说就是先尝试用malloc申请如果成功就直接返回。如果失败就调用用户设置的空间不足应对措施new-handler如果用户没设置或者设置了也没用就抛异常。所以new操作符的底层流程是先调用operator new申请空间然后在申请到的空间上调用构造函数。4.2operator deleteoperator delete底层就是调用free来释放空间没什么特别的。它加了一些调试信息和断言但核心就是free。所以delete操作符的底层流程是先调用析构函数清理对象资源然后调用operator delete释放空间。五、new和delete的实现原理5.1内置类型对于int、double这些内置类型new/delete和malloc/free其实差不多都是开空间释放空间。唯一的区别就是new失败抛异常malloc失败返回NULL。5.2自定义类型自定义类型就不一样了new和delete都要做两件事1.new的原理第一步调用operator new函数申请空间第二步在申请到的空间上执行构造函数完成对象的构造2.delete的原理第一步在空间上执行析构函数完成对象中资源的清理第二步调用operator delete函数释放对象的空间3.new T[N] 的原理第一步调用operator new[]函数而operator new[]里面实际又调用了operator new来完成N个对象空间的申请第二步在申请的空间上执行N次构造函数4.delete[] 的原理第一步在释放的对象空间上执行N次析构函数第二步调用operator delete[]释放空间operator delete[]里面又调用operator delete这里有个问题delete[]怎么知道要调用几次析构函数答案是new[]的时候会在数组前面多开4个字节32位下存对象的个数。delete[]的时候先往前读这个数字就知道要调用几次析构了。这也是为什么new[]必须配delete[]的原因——如果你用delete释放new[]的数组它只会调用一次析构然后释放空间导致内存泄漏。六、定位new表达式定位new也叫placement-new这个知识点了解就行但面试可能会问。什么是定位new简单说就是在已经分配好的内存空间上显式调用构造函数来初始化一个对象。语法格式是1new (地址) 类型2new (地址) 类型(初始化列表)来看代码你看malloc只给了我们一块原始内存不是真正的对象。定位new就是在这块内存上手动调用构造函数把它变成一个真正的对象。那什么时候会用到定位new呢主要是配合内存池使用。内存池就是提前申请好一大块内存需要用的时候从池子里拿不用每次都去堆上申请效率更高。但内存池分配出来的内存是原始的、没有初始化的如果是自定义类型就需要用定位new来显式调用构造函数。注意用定位new构造的对象销毁的时候也要显式调用析构函数然后再释放内存。顺序不能乱。七、malloc/free和new/delete的区别共同点都是从堆上申请空间都需要用户手动释放。不同点1. malloc和free是函数new和delete是操作符2. malloc申请的空间不会初始化new可以初始化。比如new int(10)直接初始化为10new A(1)直接调用构造函数初始化3. malloc申请空间时需要手动计算字节数并传递。new只需在后面跟上类型即可如果是多个对象[]中指定个数就行。比如malloc(sizeof(int)*10) 对比 new int[10]4. malloc的返回值是void*使用时必须强转。new不需要因为new后面跟的就是空间的类型返回的就是对应类型的指针5. malloc申请空间失败返回NULL所以使用时必须判空。new不需要判空但需要捕获异常6. 申请自定义类型对象时malloc/free只会开辟空间不会调用构造函数和析构函数。而new在申请空间后会调用构造函数完成对象的初始化delete在释放空间前会调用析构函数完成资源的清理
返回列表