概述
- 本文会记录CPP中对象的创建和销毁的过程中的细节
big three函数
- 拷贝构造函数
- 拷贝赋值函数
- 析构函数
用途:这三个函数是在类中包含了指针的情况下使用的。
- 当有一个成员变量是一个指针时,就有了给这个变量赋值的问题。如果这个赋值需要深拷贝,那么就需要申请内存空间。这样在析构函数中就需要对指针进行delete。
- 这个成员变量可以是在对象构造函数中被赋值的,比如是个拷贝构造函数。也可以是在拷贝赋值函数中被赋值的。
对象创建
- 使用
Complex c1(1,2)
这样的形式还是Complex* c2 = new Complex(1,2)
这样的形式,都可以创建对象。 - 构造函数不能直接被指针调用
p->P::P()
,这样写在某些编译器下可以通过编译但是某些不行。
new做了什么
生命周期
- 对于c1这样在栈上的,生命周期就是C1的作用域。
- 对于C2这样指针的,生命周期也看C2的生命周期,比如C2是个函数的局部变量,那么在函数结束时因为要调用
delete c2
,此时对象也就被销毁了。 - 如果一个c3是static的,那么按照C里面的约定这个变量的生命周期就是进程的生命周期,不管这个C3是全局还是局部。
对象在内存中是什么样的布局
看图说话
- 图是VC中的内存布局,一个对象的大小必须是16字节的倍数。
- 灰色部分是debug模式下才有的,用于debug。
- Release模式下一个对象包含上下hook共4*2=8个字节,对象本身的字节数(complex那个例子下就是实部和虚部共2个double,即4*2=8字节)。如果不够16的倍数就会出现墨绿色部分的pad,也就是填补内存。
对象的销毁
- 生成的对象c1和c2都会在作用域结束后销毁。如果是c1那么它这个对象本身就在栈上保存,因此销毁后全部内存就释放掉了。但是指针的形式,因为对象是在堆上的,如果c2指针被销毁了,那么堆上这块内存就无法找到并销毁了(直到进程结束),造成了内存泄漏。
- 因此对于指针形式的c2,在其使用完成后就可以直接调用
delete c2
来实现销毁。
析构函数
- 如果一个对象中动态的分配了内存,那么需要在析构函数找那个对分配的内存进行删除。
- 如果不显式的去写析构函数编译器应该会生成一个,如果我没有记错的话。
- 上面的c1中如果是在栈上的,那么变量离开作用域后会自动销毁并调用析构函数。
- 构造函数执行两个步骤,第一个执行
delete做了什么
数组删除
- 如果动态生产了数组内存,则在析构函数或者当需要删除对象时需要用
delete[]
。在这个过程中具体干了什么呢?
- 上面也说了,delete会先执行析构函数,其实对于一个对象来说,由于上下hook的存在因此析构函数可以完美的删除这个对象所占用的内存。所以图中左右两个delete都可以删除p所指向的数组,但是因为数组中是string的指针, 所以删除数组后string指针对指向的string对象本身并没有删除。而这个string对象本身其实就是char[]。这才是不用delete[]造成内存泄漏的本质。