目录
2.2new和delete与c语言的malloc等的本质区别
1.内存分布
1.1栈
一般的临时变量,都是存储在栈上,每一个函数都会占据一个栈帧空间,这个栈帧空间存储着这个函数的临时变量,如上面的a,mm,wwwwwww,p,j等。栈向下增长
1.2堆
注意动态开辟的空间是在堆上开辟的,除非你主动free掉,否则直到程序结束才会销毁。
但这里可能会跟上面的临时变量弄混,注意j这个指针,是开辟在栈里的,也就是说这个指针是只能在kk函数里面使用,但这个指针指向的是堆上的空间,这个空间除非free掉,否则在整个程序运行期间都可以访问。堆向上增长
1.3数据段(静态区)
还有就是全局变量和静态变量,静态变量本质上作用域没有变,如果放在全局,作用域就是当前文件里,放在函数里面,作用域就是这个函数,但在整个程序运行期间,静态变量只会定义一次,定义一次之后,再次遇到这个语句,会忽略掉。而全局变量作用域就是全局(一个工程的多个文件都能用)。两者都是开辟在数据段上的.
1.4代码段(常量区)
最后是常量和可执行代码,这两者都是只读数据,本身都不应该被修改。放在代码段里。
在栈和堆之间还有块内存映射段,这里不多解释,后期我再开个linux的专栏。
1.5图例里的空间位置分析
上面的wwwwwww是个数据,本身是在栈空间开辟,*wwwwwww也是在栈空间,因为1这个字符串常量,是在常量区也就是代码段里,而数组这边开辟的操作,本质是将这个字符串常量拷贝到了数组在栈空间开辟的空间里。
但下面的指针p不同,const修饰的是指向的char类型是个常量字符,根据字符串的规律,这里的*p指向的地址是1这个字符的地址,因此p这个指针变量是存在栈空间的,但*p(也就是这个字符串常量)是存在常量区也就是可执行代码上的。
sizeof(mm)=40;sizeof(wwwwwww)=2,sizeof(p)=4/8,strelen(wwwwwww)=2,strelen(p)=2,
sizeof(j)=4/8;
!!!!!!注意:malloc和calloc等c语言的动态函数,在我c语言的专栏里有,这里不多解释,主要是下面的new的delete。
2.C++动态内存管理
c语言的动态内存管理有点繁琐,因此c++推出了new和delete
2.1new和delete的基本使用
int main() { int* a = new int; int* a1 = new int(3); int* a2 = new int[10]; int* a3 = new int[3] {1, 2, 3}; delete a; delete a1; delete[] a2; delete[] a3; return 0; } new加类型是基本的创建 加()是初始化单个空间为()里的值,比如上面初始化为3 []是多个空间,这里形象上就是在堆上开辟了一段数组空间,有10个元素,每个元素都是int类型 多个空间初始化,可以用{}; 注意多个空间初始化,假如我们{}里面写的数量跟开辟的数量不对,比如我们开辟了10个元素大小的空间 但只初始化了3个,那么前3个元素会是我们给的值,后面的都初始化为0 delete是用来释放空间的,如果是多个元素的,比如a2,那么delete后面要加[],要匹配的。
2.2new和delete与c语言的malloc等的本质区别
对于内置类型,两者基本没有区别,只是new和delete比较方便,但对于自定义类型
malloc等无法做到初始化。
class H { public: H(int a = 0) :_a(a) { } ~H() { _a = 0; } private: int _a; }; int main() { H* a1 = new H; H* a2=new H(2); delete a1; delete a2; H aa1(1); H aa2(1); H aa3(1); H*a3=new H[3]{aa1,aa2,aa3); //这里就是通过拷贝构造初始化 H *a4=new H[3]{H(1),H(1),H(1)}; //这里是通过匿名对象进行拷贝构造初始化 H *a5=new H[3]{1,1,1}; //这里是通过隐式转换,会先构造(常量直接作为值放进去)再拷贝构造 //当然了上面的过程在编译器的优化下,都只有一次构造了。 return 0; } 对于new来说,在动态开辟空间的同时,还会自动调用一次构造函数,来初始化开辟的空间 对于delete来说,在释放动态空间的同时,还会自动调用一次析构函数。
对于自定义类型,new开的空间,是在堆上开辟了一个对象大小的空间,然后让指针指向堆上的这块空间,假如这个对象里的一个成员也是个指针,并在构造函数里new了一块空间给这个指针成员,那么这块空间是独立出来的,跟对象的空间不是在一起的,并且,是需要我们在析构函数里面delete的,因为对象的delete是针对的对象的空间,如果我们直接delete了对象空间,那么指针成员指向的空间就没有被我们主动释放,是错误的,也就是内存泄漏。
3.new和delete的实现
关于operator new和operator delete,这里不多解释,我们需要知道的是,这两个函数本质上就是封装了malloc和free。这两个函数,用法可以说跟malloc和free一模一样,就是把malloc和free的名字改下。区别就是这两个函数,比如operator new开空间失败后,是抛异常(先别管,只要知道这个机制,可以等我把异常的文章写了,或者去别的地方先看)。
之所以要有这两个函数,就是因为,new本身包含(开空间和调用构造函数),开空间如果直接用malloc,那么开空间失败后是返回空,这不符合c++的面向对象,为此封装后再使用,至于构造函数,那就直接调用即可,不用特殊处理。
当然了free没有那么多事,但为了配对,所以也有了封装,即operator delete。
总结下:
以下是针对自定义类型
1.new
先调用operator new开辟空间,对开辟的空间调用该对象的构造函数初始化。
2.delete
先对该对象的空间调用析构函数,完成后,再调用operator delete,释放对象的空间
3.new[]
先调用operator new[]开辟对象数组空间,实际上,operator new[]本质就是调用operator new开辟N个对象空间,注意不是调用N次operator new,而是直接开辟N倍的对象空间即可(实际上会多开辟4个字节,这4个字节是用来存储这个对象数组空间有多少个元素),因为是对象数组。然后在这N个对象空间里分别调用一次该对象的构造函数
4.delete[]
先在这N个对象空间里面分别调用该对象的析构函数,最后在调用operator delete[]释放整个对象数组空间,而operator delete[]本质上也是调用的operator delete,跟上面一样,直接释放整个空间(通过前面多开辟的4个字节,让delete知道要释放多大的空间)。
对于内置类型,我们只需知道,本质上就是直接malloc free。
稍微补充下,构造不可以显示调用,析构可以
4.定位new
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> class H { public: H(int a = 0) :_a(a) { } ~H() { _a = 0; } private: int _a; }; int main() { H* a1 = (H*)operator new(sizeof(H)); new(a1)H(2); //上面的语句就是定位new的写法,无参的话最后面括号可以不加 //这样就可以显示调用构造函数 a1->~H(); free(a1); return 0; }
定位new在使用中一般是配合内存池使用(减少访问堆的次数,一次申请稍微大点的空间),内存池的空间没有被初始化,所以需要二次的显示调用构造,也是用定位new。