@TOC
1. c的内存管理例题
下面这道例题用于检测c的内存管理的学习程度,又或者说是学到这里c究竟忘了多少....
- <font color=red>globalvar</font>: 是全局变量 处于静态区
- <font color=red>staticGlobalvar</font> :是全局静态变量 ,处于静态区
- <font color=red>staticvar</font> : 是局部静态变量 ,处于静态区
- <font color=red>localvar</font>: 是局部变量 ,处于 栈
- <font color=red>num1 </font> : 是一个局部的数组,处于栈</font>
<img src="https://img-blog.csdnimg.cn/7212bd14fb3a484aa63c3050137f71b5.png" width=50%>
- <font color=red>char2</font> 是一个字符数组 ,处于栈
- <font color=red>*char2</font> :char2是一个数组名,由于既不单独放在sizeof内部,也没有取地址,数组名作为首元素地址,*char2是第一个元素,而整个数组处于栈中,所以 *char2处于栈
- <font color=red>pchar3</font>: 是一个由const修饰的字符类型指针,<font
color=blue>指针指向的内容不能改变, 说明"abcd"是一个常量字符串,内容不能被修改,</font>处于栈 - <font color=red>*pchar3</font> :由于"abcd"是一个常量字符串,pchar3指向常量字符串,*pchar3 处于常量区
- <font color=red> ptr1</font> :是一个指向堆开辟空间的指针,处于栈
- <font color=red>*ptr</font> :是为堆开辟的空间 ,处于堆</font>
- <font color=red> sizeof(num1)</font>: 单独当在sizeof内部,数组名代表整个数组,sizeof(num1)=40
- -<font color=red> sizeof(char2)</font>:单独当在sizeof内部,数组名代表整个数组,abcd\0,sizeof(char2)=5
- -<font color=red> sizeof(pChar3)</font>:pChar3是一个指针,所以sizeof(pChar3)=4/8
- <font color=red>strlen(pChar3)</font>:pChar3代表首元素地址,<font color=blue>strlen为从给予的地址开始 到'\0'结束,strlen(pChar3)=4
- -<font color=red> sizeof(ptr1)</font>:ptr1是一个指针 ,sizeof(ptr1)=4/8
<img src="https://img-blog.csdnimg.cn/bdc4c976aae242cc8d3cd0f06ef93430.png" width=80%>
2.c++管理方式
1.c++的内置类型
1.申请一个空间并初始化
<img src="https://img-blog.csdnimg.cn/aa249fc88b19472896dc5a64a5556b29.png" width=80%>
2.申请连续的空间并初始化
<img src="https://img-blog.csdnimg.cn/ec403490c87d4315932f04e756efd3af.png" width=80%>
这里相当于部分初始化,只初始化了前4个空间,其他空间默认为0
3.总结
申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[]
2.c++的自定义类型
- 申请2个A类型的空间,调用2次构造函数释放空间,并调用2次析构函数,虽然写入了malloc在堆开辟10个A类型空间,free释放空间,但是没有调用构造和析构函数
<img src="https://img-blog.csdnimg.cn/924ae1fbb7f44af5ba4565c883501267.png" width=70%>
2.总结
在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会。
3.operator new与operator delete函数
- operator new与operator delete函数是库里面提供的两个全局函数,不是运算符重载
new和delete是用户进行动态内存申请和释放的操作符,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。
<img src="https://img-blog.csdnimg.cn/d8b70841731a49e5937bef20915485af.png" width=80%>
<img src="https://img-blog.csdnimg.cn/b3e58f6c007a4ea3a7f641a26975c4f3.png" width=80%>\
4.new和delete的实现原理
1.内置类型
- 对于是内置类型,malloc/free与new/delete功能基本一致,但new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间
- new机制与malloc也不同,new申请空间失败会抛异常,而malloc失败返回nullptr
2.自定义类型
- new先申请一个A的空间,再调用构造函数,delete先调用析构函数,再释放空间
- new先申请10个A类型的空间,再调用构造函数10次,delete先调用析构函数10次,再释放空间
内存泄露问题&&delete先析构的原因
<img src="https://img-blog.csdnimg.cn/3b2608b2b75e42d1b6411189dcac0bb7.png" width=90%>
- 类的实例化对象生成p,在栈上,调用构造函数,在堆上开辟了4个stack类型的数组
- p1是一个指针,在栈上,指向在堆上申请的一个stack, 再调用构造函数,_a=new stack[4],_a再次指向在堆上申请的4个stack类型的数组,
所以必须先调用析构函数,在释放空间
<img src="https://img-blog.csdnimg.cn/6d959019570f46d9b9f56f478d00d424.png" width=60%>
若将delete p1改为 free(p1),会少调用析构函数,直接释放stack空间 导致无法释放堆上申请的4个stack类型的数组,从而导致内存泄露
编译器实现机制问题
正常来说,A*p=new A[10],我们知道会调用10次构造函数,但是delete [] p是怎么知道要调用10次析构函数的呢?
<img src="https://img-blog.csdnimg.cn/24ea16da35504204a68c8bc63cc845f6.png" width=40%>
- 自定义类型A的大小为4个字节,申请10个A类型的数组,会开辟40个字节的空间,但是编译器会多开辟4个字节,用于存储个数 10,个数10是给delete时候用的free ( p ) / delete p 时,释放的位置不对,所以会报错delete[],就从当前指针p指向位置的地址往前减去4个字节,取到这个值(例如10),通过这个值就知道调用多少次析构函数
<img src="https://img-blog.csdnimg.cn/5e3aacf5968e4831bc904bab35784943.png" width=40%>
- 最终指针指向释放位置,从释放位置开始释放空间
5.定位new
- 对一块已有的空间进行初始化
定位new的使用场景
<img src="https://img-blog.csdnimg.cn/809484a2ecf441c98a2d31c2259bb2f6.png" width=50%>
- 操作系统的堆因为给所有的地方提供,所以会慢一些
- 以前使用malloc/new申请内存,都是去操作系统的堆上申请的,直接申请
- 为了提高效率,申请内存去内存池中寻找,而内存池中内存也是堆上的,
如果内存池上有就直接返回,如果没有就会去堆上找,比如需要4个字节,内存池会申请大块的内存,储备到内存池中,下一次来申请内存,就能在内存池中找到 - 当在内存池中要的内存,而内存池要的内存没有初始化,所以需要定位new
6.malloc/free与new/delete的区别
1.共同点
都是从堆上申请空间,并且需要用户手动释放</font>
2.不同点
用法角度
- 1.malloc和free是函数,new和delete是操作符
- 2.malloc申请的空间不会初始化,new可以初始化
- 3.malloc申请空间时,需要手动计算空间大小并传递,new只需其后跟上空间的类型即可,如果是多个对象时,[]指定对象个数即可
- 4.malloc返回值为void*,在使用必须强转,new不需要,因为new后跟的是空间类型</font>
底层原理角度
- malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new失败会抛异常
申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造和析构函数,而new申请空间后会调用构造函数完成对象的初始化,delete在释放空间会调用析构函数完成空间中的资源的清理