一個C++bug引入的許多知識


 

 

一、前言

    假設我們有一個Car類,用了表示一個車,它有id,名字,牌照等許多東西,還有一個表示車的部件CarPart。

    但出於某方面的考慮,我們不打算在產生car這個對象的時候,就生產出這個車,你可以認為這個時候,只有一個紙糊的車擺在你的面前,它有id,有名字,有牌照,但是它不能動,只有我們打算啟動這個車的時候,才去給這個車配置發動機,輪胎等各個部件。

 

二、錯誤代碼1

//CarPart類  用了標識車內的各個部件

  NewImage

//Car類 用了標識車 

NewImage

 

我們定義了一個car類,它里面有一個_id標識這個car,也有一個_car來標識這個車的各個部件,在最開始的時候,_car指針是null,當我們調用getCar的時候,我們判斷這個車是否創建好了部件,有的話就返回部件,沒有的話,為這個車創建部件,至於具體的創建步驟,也許是在工廠制造,也許是從其他地方搶來的也有可能,然后返回車的部件

 

main函數

NewImage

我們在一個循環里來創建car對象,創建這個車的部件,並把這個對象放進一個vector里,在這個循環里,我們只會循環一次,至於原因你在下面會看到

然后我們運行程序,剛開始看起來很正常,但是糟糕…程序出現了問題

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的時候,造成錯誤

 

三、錯誤代碼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時就會出現問題

問題的根源依舊是沒有深拷貝構造函數

 

 

 

 

四、結論

1、賦值函數,拷貝構造函數,析構函數通常應該被視為一個整體,即需要析構函數的類也需要賦值函數和拷貝構造函數,反之亦然

2、為了支持快速訪問,vector將元素連續儲存,當不得不獲取新的內存空間的時候,vector會其他地方申請新的空間,並將元素從舊的地方移動到新的地方,這期間會調用元素的析構函數和拷貝構造函數

3、C++中堆內存是可以復用的,當你釋放一塊內存之后,又立即申請一塊內存,新申請的內存空間很可能在剛剛釋放的內存上

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM