三、错误代码2
我们刚刚看了一个版本的错误代码,现在我们来看看另一个版本的错误代码
CarPart和Car类和上一个版本的一样
main函数有所不同
这里我们没有直接操作temp对象,而是通过vcar.back()获取刚刚push_back进去的对象,并在它上面进行getCar操作,这样就避免了temp和vcar[0]中的指针指向同一块内存
我们运行程序,看起来一切正常
Start
Make 4 tires of car 0
Make engine of car 0
-------------------
End
然后,我们把第10行 稍作一下修改,让它循环2次,再次运行,该死,程序又出错了
Start Make 4 tires of car 0 Make engine of car 0 ------------------- Make 4 tires of car 1 Make engine of car 1 ------------------- End *** glibc detected *** double free or corruption (fasttop): 0x0000000000503030 *** |
查看core文件,发现又是在析构函数处出现了问题
(gdb) bt #0 0x0000003f0b02e2ed in raise () from /lib64/tls/libc.so.6 #1 0x0000003f0b02fa3e in abort () from /lib64/tls/libc.so.6 #2 0x0000003f0b062d41 in __libc_message () from /lib64/tls/libc.so.6 #3 0x0000003f0b06881e in _int_free () from /lib64/tls/libc.so.6 #4 0x0000003f0b068b66 in free () from /lib64/tls/libc.so.6 #5 0x000000342cfae19e in operator delete () from /usr/lib64/libstdc++.so.6 #6 0x0000000000400f80 in ~Car (this=0x504080) at Car.h:42 #7 0x0000000000401b65 in std::_Destroy<Car> (__pointer=0x504080) at /usr/lib/gcc/x86_64-redhat-linux/3.4.5/../../../../include/c++/3.4.5/bits/stl_construct.h:107 #8 0x00000000004019d5 in std::__destroy_aux<Car*> (__first=0x504080, __last=0x5040a0) at /usr/lib/gcc/x86_64-redhat-linux/3.4.5/../../../../include/c++/3.4.5/bits/stl_construct.h:120 #9 0x0000000000401411 in std::_Destroy<Car*> (__first=0x504060, __last=0x5040a0) at /usr/lib/gcc/x86_64-redhat-linux/3.4.5/../../../../include/c++/3.4.5/bits/stl_construct.h:152 #10 0x0000000000401259 in ~vector (this=0x7fff3ead6110) at /usr/lib/gcc/x86_64-redhat-linux/3.4.5/../../../../include/c++/3.4.5/bits/stl_vector.h:256 #11 0x0000000000400ea2 in main () at main.cpp:18 (gdb) |
为什么把循环从一次改成两次就会出错了呢
我们进如果打印vcar里对象中_car的地址,会发现他们竟然是一样的
那么这又是为什么呢
在C++中,堆内存是存在复用的可能的,如果上一个内存已经被释放调,在new新对象的时候,新对象的内存便可能建立在刚刚释放的内存上
我们知道vector内部是类似数组的连续的储存空间
vector在发现空间不足时,会在其他地方重新申请一块内存空间,调用原来对象的拷贝构造函数 在新的地方进行创建,并把原来地方的对象析构调
第一次循环的时候 vector的大小是1,容量也是1,在第二次调用,由于这个时候,放进了第二个元素,所以vector的大小需要进行调整,便在新的地方重新申请了一块内存,调用了car的拷贝构造函数,并将原来的对象进行析构,所以导致了第二次创建的对象的_car地址和第一个对象一样
这样当程序结束调用析构函数的时候,由于vcar[0]和vcar[1]中_car指向同一块内存,在delete时就会出现问题
问题的根源依旧是没有深拷贝构造函数
四、结论
1、赋值函数,拷贝构造函数,析构函数通常应该被视为一个整体,即需要析构函数的类也需要赋值函数和拷贝构造函数,反之亦然
2、为了支持快速访问,vector将元素连续储存,当不得不获取新的内存空间的时候,vector会其他地方申请新的空间,并将元素从旧的地方移动到新的地方,这期间会调用元素的析构函数和拷贝构造函数
3、C++中堆内存是可以复用的,当你释放一块内存之后,又立即申请一块内存,新申请的内存空间很可能在刚刚释放的内存上