一个C++bug引入的许多知识

 

 

1、前言

    假设咱们有一个Car类,用了表示一个车,它有id,名字,牌照等许多东西,还有一个表示车的部件CarPart。linux

    但出于某方面的考虑,咱们不打算在产生car这个对象的时候,就生产出这个车,你能够认为这个时候,只有一个纸糊的车摆在你的面前,它有id,有名字,有牌照,可是它不能动,只有咱们打算启动这个车的时候,才去给这个车配置发动机,轮胎等各个部件。c++

 

2、错误代码1

//CarPart类  用了标识车内的各个部件数组

  NewImage

//Car类 用了标识车 函数

NewImage

 

咱们定义了一个car类,它里面有一个_id标识这个car,也有一个_car来标识这个车的各个部件,在最开始的时候,_car指针是null,当咱们调用getCar的时候,咱们判断这个车是否建立好了部件,有的话就返回部件,没有的话,为这个车建立部件,至于具体的建立步骤,也许是在工厂制造,也许是从其余地方抢来的也有可能,而后返回车的部件this

 

main函数spa

NewImage

咱们在一个循环里来建立car对象,建立这个车的部件,并把这个对象放进一个vector里,在这个循环里,咱们只会循环一次,至于缘由你在下面会看到3d

而后咱们运行程序,刚开始看起来很正常,可是糟糕…程序出现了问题指针

g++   -g  main.cpp -o main.out  //(使用-g选项来生成调试文件)调试

 

 ./main.out  对象

 

Start
Make 4 tires of car 0
Make engine of car 0
-------------------
End
*** glibc detected *** double free or corruption (fasttop): 0x0000000000503010 ***

 

咱们看到程序出现了一些问题,产生了一个core文件

咱们用gdb查看一下这个core文件

gdb main.out

(gdb) core-file core.45393 

(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  0x0000000000400dc0 in ~Car (this=0x503030) at Car.h:29

#7  0x00000000004016fd in std::_Destroy<Car> (__pointer=0x503030) at /usr/lib/gcc/x86_64-redhat-linux/3.4.5/../../../../include/c++/3.4.5/bits/stl_construct.h:107

#8  0x000000000040155b in std::__destroy_aux<Car*> (__first=0x503030, __last=0x503040) at /usr/lib/gcc/x86_64-redhat-linux/3.4.5/../../../../include/c++/3.4.5/bits/stl_construct.h:120

#9  0x0000000000401103 in std::_Destroy<Car*> (__first=0x503030, __last=0x503040) at /usr/lib/gcc/x86_64-redhat-linux/3.4.5/../../../../include/c++/3.4.5/bits/stl_construct.h:152

#10 0x0000000000400f89 in ~vector (this=0x7fff0f7371a0) at /usr/lib/gcc/x86_64-redhat-linux/3.4.5/../../../../include/c++/3.4.5/bits/stl_vector.h:256

#11 0x0000000000400d0a in main () at main.cpp:17

 

咱们看到程序是从程序的第17行结束,调用析构函数时出现的问题

析构函数出错的缘由通常是屡次释放同一块内存

那么这里的问题出如今那里呢?

咱们想想第12行咱们建立了一个temp对象,而后第13行为这个temp对象建立了汽车组件

这个时候的内存看起来是这个样子

NewImage

接着咱们把temp放进了vector中,这个时候会调用car的拷贝构造函数,因为car没有定义本身的拷贝构造函数,所以将会执行默认的拷贝构造函数进行浅拷贝操做

这个时候的内存是这个样子

 

NewImage

当第一次循环结束的时候,temp被析构,汽车组件被delete掉

而后当程序结束的时候,对vcar[0]进行析构,因为Temp中的_car和Vcar[0]中的_car对象指向了同一块内容,vcar[0]所指的汽车组件已经被释放掉,再次delete的时候,形成错误

 

3、错误代码2

咱们刚刚看了一个版本的错误代码,如今咱们来看看另外一个版本的错误代码

CarPart和Car类和上一个版本的同样

main函数有所不一样

NewImage

这里咱们没有直接操做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的地址,会发现他们居然是同样的

NewImage

NewImage

那么这又是为何呢

在C++中,堆内存是存在复用的可能的,若是上一个内存已经被释放调,在new新对象的时候,新对象的内存即可能创建在刚刚释放的内存上

 

咱们知道vector内部是相似数组的连续的储存空间

vector在发现空间不足时,会在其余地方从新申请一块内存空间,调用原来对象的拷贝构造函数 在新的地方进行建立,并把原来地方的对象析构调

第一次循环的时候 vector的大小是1,容量也是1,在第二次调用,因为这个时候,放进了第二个元素,因此vector的大小须要进行调整,便在新的地方从新申请了一块内存,调用了car的拷贝构造函数,并将原来的对象进行析构,因此致使了第二次建立的对象的_car地址和第一个对象同样

这样当程序结束调用析构函数的时候,因为vcar[0]和vcar[1]中_car指向同一块内存,在delete时就会出现问题

问题的根源依旧是没有深拷贝构造函数

 

 

 

 

4、结论

一、赋值函数,拷贝构造函数,析构函数一般应该被视为一个总体,即须要析构函数的类也须要赋值函数和拷贝构造函数,反之亦然

二、为了支持快速访问,vector将元素连续储存,当不得不获取新的内存空间的时候,vector会其余地方申请新的空间,并将元素从旧的地方移动到新的地方,这期间会调用元素的析构函数和拷贝构造函数

三、C++中堆内存是能够复用的,当你释放一块内存以后,又当即申请一块内存,新申请的内存空间极可能在刚刚释放的内存上

相关文章
相关标签/搜索