程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 內存陷阱:馴服C++中的野指針

內存陷阱:馴服C++中的野指針

編輯:關於C++

 什麼是野指針?

一個母親有兩個小孩(兩個指針),一個在廚房,一個在臥室,(屬於不同的代碼塊,其生存期不同)母親讓在廚房的小孩帶一塊蛋糕(指針指向的對象)給在臥室的小孩,這樣在臥室的孩子才肯寫作業。但這個在廚房的小孩比較淘氣,他在走出廚房時自己將蛋糕吃了,沒能帶出來。而在臥室的沒有吃到蛋糕,所以不肯完成他的作業。結果母親卻不知道臥室的孩子沒有吃到蛋糕,還以為作業完了。結果第二天她就被老師召喚到辦公室了。事情麻煩了。

這樣,那個在臥室的孩子就是野指針了,因為他沒有得到應得的蛋糕,不能完成母親交給他的作業。

這就是c中所講的野指針。上面的小劇本不過演示了一種最基本的野指針的形成過程。更容易出現的情形是coder在編碼時,大意之下使用了已經free過的指針。

對於年輕點的經驗欠缺的coder來說是比較容易犯的錯誤,經驗老到的程序員或者慎重采取成對編程的形式避免這種失誤,或者使用引用計數器防止形成野指針。

總之,在c中,野指針也許性子野,但是控制起來也是有章可循。然而事情在c++中出現了變化。

coder們面臨更大的麻煩了。c++程序員無可避免的要寫很多這樣那樣的類。誰讓c++是面向對象的呢?

我們在寫類的時候難免要用new給類的數據成員分配內存。這本來沒什麼,動態分配內存是一種很常見的基本操作,我們在學數據結構時經常這麼做,不是麼?

但是伙計,事情並非這麼簡單。類是一種高級的用戶自定義數據類型,看起來和結構、枚舉這樣的用戶自定義類型沒啥太大差別。如果你這樣認為....?那你會死的很慘。類太復雜了,普通情況下使用類的對象並沒有太大的問題,但是,當你要復制一個對象時,問題就來了。

比如我們知道,你要用一個對象初始化另一個對象時,c++是按位進行拷貝的,即在目標對象裡創建了初始化對象的一個完全相同的拷貝。這在多數情況下已經足夠了。但是,當你的類在創建時為每個對象分配內存,也就是說類中有new操作。當你的對象創建好後,類也為對象分配了一塊內存。如果你用這個對象去初始化另一個對象時,被初始化的對象和初始化的對象完全一樣。這意味著,他們使用同一塊內存,而不是重新為被初始化的對象分配內存。

這樣麻煩就大了。如果一個對象銷毀了,那麼分配的內存也就銷毀了(別忘了,類是有析構函數的,它負責在對象銷毀時,釋放動態分配的內存。難道你說你不在類中寫上析構部分?那麼可憐的孩子,那你就走向了另一個深淵,當你的程序運行數小時之後,系統會告訴你,內存不夠用了。想象一下把你的程序用在騰訊的服務器上),另一個對象就殘缺不全了,這就像一對連體嬰兒,他們共用了一部分器官,心髒或者肝髒。要救活一個,就犧牲了另一個。一個得病了,另一個也要遭殃。

可以說,這就是c++中更加變態的野指針。

什麼?你說我不用對象初始化對象?那麼我們會不會將一個對象作為變元傳遞給函數呢?我們很多時候都這樣做。有時我們不得不將對象按值傳遞給一個函數,但是你要知道,按值傳遞是什麼意思?它的意思就是,把實參的一個拷貝傳遞給函數。這和剛才的初始化沒什麼兩樣,按位拷貝,函數體內的對象與外面的對象共用一塊內存,即便在函數中的對象沒有對這塊內存進行過操作,但是當函數結束時。。。。析構函數將會被調用......

還有一種與之相反的情況......, 當你想要把一個在函數內的對象值返回給外面的對象時,這時候,會自動產生一個臨時對象,由它容納函數的返回值,並在函數結束時把結果傳給目標。那麼這個臨時對象迅速的被創建,並被迅速的釋放。。。一塊內存被釋放了兩次。其後果是不可預見的。

當你把一個對象的值賦給另一個對象時,如果你沒有重載賦值運算符,那麼也會導致按位拷貝。最終產生一個野指針(一個隱藏在類內的毒瘤),或者釋放同一塊內存多次。

看到了麼?害怕了麼?是不是感到C++到處都是陷阱呢?不但有陷阱,到處都是危險品。所有c中的疑難問題,到了c++就成了一般問題了。好了不廢話了,我們繼續講講解決之道。

對於最後的這種賦值的情況,我們只有通過重載賦值運算符才能解決,也就是避免按位拷貝。

至於前面的都屬於初始化,概括下來就是三種情況:

1.當一個對象初始化另一個對象時,例如在聲明中;

2.把所創建的對象拷貝(按值)傳遞給一個函數時;

3.生成臨時對象時,最常見的就是函數的返回值。

解決初始化時的按位拷貝問題,我們通過創建拷貝構造函數來解決。

基本的拷貝構造函數形式為:

classname (const classname &o)

{

//body here

}

拷貝構造函數就是針對這個問題而設計的。

恩,大家都明白了吧?不要讓你的對象都變成可憐的連體人啊~~~~

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved