一個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++中堆內存是能夠複用的,當你釋放一塊內存以後,又當即申請一塊內存,新申請的內存空間極可能在剛剛釋放的內存上

相關文章
相關標籤/搜索