深入探索C++对象模型(十)执行期语义学(new和delete)
关于new和delete运算符。
a. 使用new运算符构造对象时, 例如:
- Point3d* origin = new Point3d;
会被转化为两个操作:分配空间和调用类的构造函数:
- Point3d* origin;
- if(origin = __new(sizeof(Point3d)))
- {
- origin = Point3d::Point3d(origin);
- }
- delete origin;
- if(0 != origin)
- {
- Point3d::~Point3d(origin);
- __delete(origin);
- }
- Point3d* p_array = new Point3d[10];
- Point3d* p_array;
- p_array = vec_new(0, sizeof(Point3d), 10, &Point3d::Point3d, &Point3d::~Point3d);
c. Placement operator new语义
Placement new是一个预先定义好的重载的new运算符,其作用是在已经申请好的内存上直接构造对象,例如:
- Point2w* ptw = new(arena) Point2w;//arena为已申请内存的地址
- void* operator new(size_t, void* p)
- {
- return p;
- }
- Point2w* ptw = (Point2w*) arena;
- if(0 != ptw)
- {
- ptw->Point2w::Point2w();
- }
- Point2w* ptw = new(arena) Point2w;
- //...do something
- ptw = new(arena) Point2w;
而现有object有一个析构函数,那么改析构函数不会被调用,我们知道,调用delete object会调用该object的析构函数,但是此处不可以这样做,因为,delete不但会调用析构函数,而且会释放内存,那样arena就不能继续使用了,所以我们需要做的是仅仅调用object的析构函数:
- ptw->~Point2w();
注:标准C++提供了placement operator delete,它会调用析构函数而不释放内存。
为什么new/delete和new[]/delete[]必须配对使用?
new和delete的内部机制这里不赘述了,戳这里《浅谈 C++ 中的 new/delete 和 new[]/delete[]》
glibc的mallc和free实现的内存分配释放简介,戳这里《malloc和free的内存到底有多大?——GNU glib库》
第一篇博客讲的很好,但是最后new、delete的为什么配对使用的解释单纯理解还不到位。这里总结并补充说明一下。
动态内存使用表
分配方式 | 删除方式 | 结果 | 结果分析 |
---|---|---|---|
new | delete | 成功 | 合法 |
new | delete[] | 失败 | 参考下文Q1 |
new[] | delete | 内嵌类型成功;自定义类型失败 | 参考下文Q2 |
new[] | delete[] | 成功 | 合法 |
合法的new/delete和mallc/free示意图
说明:如果对象按照字节对齐的话,那么对象之间可能存在填充,因此图中的对象间隔不一定存在。
前提知识1:第一篇博客中已经阐明:new [] 时多分配 4 个字节,用于存储用户实际分配的对象个数。而new不会多分配。
前提知识2:在第二篇博客中,介绍了mallc和free的实际大小,这个信息存储在内存块的块头里面。其中最重要的就是指示实际分配的内存大小(单位:字节),那么在free时,就要将用户的传入的地址,减去块头长度找到实际分配内存的起始地址然后释放掉。块头的长度是8字节。
知道这两个前提知识下面两个问题就好解释了。
Q1: new为什么使用delete[]失败?
new时不会偏移4字节,delete[]时,编译器就是通过标识符[]而执行减4字节操作。从上图可知,减后的地址值会落到块头中,同时编译器从块头中提取4字节中的值作为自己执行析构对象的个数,而这4个字节是实际内存的长度,这是一个比较偏大的值,比如256,然后编译器对随后的内存对象执行析构,基本上都会导致内存越界,这必然失败。
Q2:new[]为什么内嵌类型使用delete成功,自定义类型delete失败?
new[],如果是内嵌类型,比如char或者int等等,就是C数组,那么它不会向后(地址变大的方向)偏移4个字节。因此执行delete时,显然不会出现任何问题。
但是如果是自定义类型呢?那么new[]时就会向后偏移4个字节,从malloc的返回地址偏移4个字节用来存储对象个数,如果使用delete,编译器是识别不出释放的是数组,那么它会直接将传入对象的首地址值处执行一次对象析构,这个时候还不会出现问题,但是再进一步,它把对象的首地址值传递给free时,那么这个地址值并不是malloc返回的地址,而是相差了4个字节,此时free向前偏移取出malloc的实际长度时,就会取出对象的个数值作为实际的分配长度进行释放,显然这将导致只释放了n字节,其余的块头一部分和除n字节的所有内存都泄露了,并且只有第一个对象成功析构,其余都没有析构操作。一般对象个数n是个非常小的值,比如128个对象,那么free只释放了128字节。(注意:不同的libc实现不同,这里只示例阐述原理,不深究数字)
说起new和delete,了解过c++的人应该都知道吧,它是用来分配内存和释放内存的两个操作符。与c语言中的malloc和free类似。
c语言中使用malloc/calloc/realloc/free进行动态内存分配,malloc/calloc/realloc用来在堆上分配空间,free将申请的空间释放掉。
malloc:
- void FunTest()
- {
- int *pTest = (int*)malloc(10*sizeof(int)); //开辟10个int型的空间大小
- if(pTest != NULL)
- {
- free(pTest);
- pTest = NULL;
- }
- }
- void FunTest()
- {
- int *pTest = (int*)calloc(10,sizeof(int)); //分配10个int型的内存块,并将其初始化为0
- if(pTest != NULL)
- {
- free(pTest);
- pTest = NULL;
- }
- }
- void FunTest()
- {
- int *pTest = (int*)malloc(10*sizeof(int));
- realloc(pTest,20*sizeof(int)); //改变原有空间大小,若不能改变则会新开辟一段空间,并将原有空间的内容 拷贝过去,但不会对新开辟的空间进行初始化
- free(pTest);
- }
对于内存泄漏,我介绍一下我所见过的内存泄漏吧:
(1)申请内存但并未释放。
- void FunTest()
- {
- int *pTest1 = (int*)malloc(10*sizeof(int));
- *pTest1 = 0;
- }
①同一块空间释放两次,导致崩溃;
②有一块空间没有释放,以为释放了,导致内存泄漏。
- void FunTest()
- {
- int *pTest1 = (int*)malloc(10*sizeof(int));
- int *pTest2 = (int*)malloc(10*sizeof(int));
- pTest1 = pTest2;
- free(pTest1);
- free(pTest2);
- }
- void FunTest()
- {
- char *pTest1 = (char*)malloc(5);
- strcpy(pTest1,"hello world");
- free(pTest1);
- }
- void FunTest()
- {
- int *pTest1 = (int*)malloc(10*sizeof(int));
- assert(pTest1 != NULL);
- pTest1[0] = 0;
- pTest1++; //地址向后移动了一位
- free(pTest1);
- }
c++中是通过new和delete操作符进行动态内存管理的。
先用一张图简单的说明一下new和delete的含义:
记住:new和delete就像malloc和free一样,都要成对使用哦。
我们再看一个这样的表达式:
- string *s = new string("a value"); //分配并初始化一个string对象
- string *str = new string[10]; //分配10个默认初始化的string对象
这是string *s = new string("a value"); 这句表达式内部的实现:
我们可以看出new内部的调用顺序:(初始化一个对象时)
new内部的调用顺序:(初始化若干个对象时)
同样地,delete对象时,调用顺序为:(delete单个对象时)
delete对象时,调用顺序为:(delete多个对象时)
接下来,看一下动态内存分布图:
new和delete与malloc和free一样,都是存在堆上的。那么,二者有什么差别呢?
· 总结new/delete和malloc/free的区别和联系:
1. 它们都是动态管理内存的入口。
2. malloc/free是C/C++标准库的函数,new/delete是C++操作符。
3. malloc/free只是动态分配内存空间/释放空间。而new/delete除了分配空间还会调用构造
析构函数进行初始化与清理(清理成员)。
4. malloc/free需要手动计算类型大小且返回值为void*,new/delete可自己计算类型的大小
对应类型的指针。
5.new/delete的底层调用了malloc/free。
6.malloc/free申请空间后得判空,new/delete则不需要。
7.new直接跟类型,malloc跟字节数个数。