2011-05-11 70 views
1

我無法找到我編寫的下面的代碼中的錯誤[雖然沒有任何用途]。 只有在派生類時纔給出SEG FAULT的代碼!

 

#include < iostream > 
#include < cstdlib > 

using namespace std; 


class Base{ 
public: 
     Base(){cout << "Base class constructor" << endl;} 
     void funv() {}; 
     ~Base(){cout << "Base class destructor" << endl;} ; 
}; 

class Derived:public Base{ 
public: 
     char *ch; 
     Derived():ch(new char[6]()){} 
     ~Derived(){ 
       cout << "before" << endl; 
       delete [] ch; 
       ch = NULL; 
       cout << "after" << endl; 
     } 
}; 

int main(){ 

     Derived * ptr = new Derived; 

     //memcpy(ptr -> ch,"ar\0",4); // Works when class Derived is derved from base and also when not derived from base 

     ptr -> ch = const_cast < char* >("ar0"); // Works only when class Derived is not derived from class Base 

     cout << ptr -> ch[1] << endl; 

     ptr -> funv(); 

     delete ptr; 

     return 0; 
} 

 

我評論的代碼行嫌上。

我使用的Sun Studio 12

+0

-1:不知道爲什麼你認爲這將是安全的。 – 2014-07-07 17:21:03

+0

@LightnessRacesinOrbit:是的,在問了這個問題超過3年後,即使我現在不知道爲什麼我認爲它會安全:)。感謝您使它注意到 – Arunmu 2014-07-07 18:49:50

回答

2

這是一個未定義行爲。無論你是否得到它,它都會導致問題。 當分配const char*char*象下面這樣:

ptr -> ch = const_cast < char* >("ar0"); 

這意味着,要分配其在非堆段(主要是在數據段)中所定義的字符串。應該只有delete內存,它被分配在堆段上。

另外,上面的賦值語句被執行,它會在之前泄漏內存指向ch。爲了避免這樣的問題的一個方法是隻要你嘗試ch分配一些變量聲明爲,

private: char* const ch; 

,它會給編譯錯誤。所以它會讓你編寫包裝來分配ch,在那裏你可以照顧取消分配。

+0

是的。你是對的。但是當我不是從基類派生出來的時候,爲什麼我沒有得到seg錯誤?或者是由於UB我沒有收到任何seg故障? – Arunmu 2011-05-11 05:55:30

+0

是的,這是由於未定義的行爲。在其他編譯器中可能會出現段錯誤。但它是一個純粹的UB。 – iammilind 2011-05-11 05:57:54

-1
memcpy(ptr -> ch,"ar\0",4); 

memcpy copys從源緩衝區的內容,目標緩衝區指針ptr->ch點。

ptr -> ch = const_cast < char* >("ar0"); 

您將重新分配值給指針本身。這很危險,因爲您無法再獲取堆上的原始緩衝區。這是內存泄漏。另外,在你的析構函數中你刪除了這個指針,它現在並不指向堆上的緩衝區。它沒有定義。

+0

不留言評論是非常專業的。沒有理由? – 2011-05-11 06:00:12

+0

我一次只執行一項操作。不能同時進行。對於測試puposes我留下兩個陳述之一評論。 – Arunmu 2011-05-11 06:03:32

+0

@Eric,我認爲有人可能因爲你對'memcpy'的引用而被拒絕了;該行在代碼中被註釋;所以它沒有任何關聯。我建議,你應該從你的答案中刪除該行。 – iammilind 2011-05-11 06:05:25

1

我認爲你對理解Undefine Behavior的含義有嚴重的問題。

未定義行爲並不一定意味着一個段錯誤

遺憾的呼喊,但是這是一個非常importan點。

未定義的行爲意味着,如名稱所示,「未定義」。這意味着你不知道會發生什麼。實際上,段錯是你可以期待的最好的事情......但不幸的是,90%的UB在C++中只是沉默。

該應用程序將正常運行,甚至會給你你期望的結果。一切都會好起來的......直到當天演示當天,在百人面前,應用程序將非常糟糕地崩潰,只是爲了讓你在YouTube上眩暈的表情。

未定義的行爲是通過實驗學習C或C++是一個壞主意的原因之一。當你在C++中犯錯誤時,語言不會幫助你......沉默的假設基本上是你不會犯任何錯誤......如果你正在學習語言,這當然是一個非常困難的要求。

未定義的行爲也是在C或C++編寫時,在測試套件中放置太多希望也是一個壞主意的原因。在這些語言中(以及UB存在的其他語言),編寫代碼時不需要太多思考(因此會導致錯誤),然後希望稍後將其刪除。雖然這種將思考時間首先節省時間並且隨後用調試時間進行交易的想法是海事組織通常是一種糟糕的做法(錯誤消除的成本/努力總是會更高),但是在允許UB的語言中,這是一種真正的自殺,因爲錯誤可以隱藏也落後於非確定性行爲。

顯然,我不是說測試是一個糟糕的主意......這當然是一個偉大的(基本上是,如果你想保持一個重構的可能性必須具備的),如果它不作爲,但只有在編寫代碼時請原諒降低您的注意力。

你應該基本上避免混淆錯誤(即UB)與什麼是崩潰(即段錯誤)。崩潰是朋友......並且您希望儘可能多地崩潰,因爲崩潰是指出現錯誤的信號。要添加更多可能的崩潰點,例如,您應該使用斷言...... segfault只是環境自動放置的斷言,但您需要更多。當你發生崩潰時,你知道有一個錯誤,你可以開始尋找它。不幸的是,當你沒有崩潰時,這並不意味着沒有錯誤......它只是意味着沒有錯誤落在你爲他們準備的陷阱中。

一段代碼可以很好地編譯(零錯誤和零警告),它可以通過整個測試套件......但仍然可能同時不正確。當然,即使在高級語言中,確定避免UB的性能價格值得運行時檢查(例如Java),但是更高的邏輯級別也是如此。由於非確定性,C和C++中的UB使事情變得更加困難。

在您的代碼中,UB被觸發是因爲您在調用new ... [](字符串文字)時沒有獲得指針時調用delete[]。這當然可能會或不會產生段錯誤。

您的代碼也有一些其他的法律,但很值得懷疑部分:

  1. 的基類的析構函數不是虛擬的。你不是使用指向基的指針來銷燬派生的對象,所以這是合法的,但不管怎麼說都不是一個壞主意。如果一個類是要派生的,那麼析構函數應該是虛擬的。

  2. 派生類定義了析構函數,但沒有複製構造函數和賦值運算符。 These three methods should always go together:要麼你沒有定義它們,要麼你定義(或者至少聲明)它們三個。原因是這三種方法是由編譯器自動創建的,如果它們不存在的話,那麼自動生成的代碼在一種情況下是正確的,而在其他情況下則不太可能。例如,在你的代碼中,如果有人用Derived *der2 = new Derived(*der1);複製了派生對象,那麼指向動態分配內存的指針將被複制並且可能被釋放兩次(兩個對象被銷燬一次)。如果使用*der2 = *der1;進行分配,除了內存泄漏之外,您還可能會發生雙重釋放。禁止一個類的拷貝構造函數和分配可能是有意義的,但在這種情況下,常見的習慣用法是將這些方法聲明爲私有的並使它們不能實現(如果其他人試圖使用它們,這會產生編譯錯誤,並且鏈接錯誤,如果一種方法錯誤地使用它們)。

+0

寫得很好,但從我得到的答案是10%的圖片,即「由於UB的Seg故障」。除了將字符串文字分配給指向已分配內存的字符指針以外,還有其他可能的缺陷嗎? (我認爲這個缺陷很大,但仍然:)) – Arunmu 2011-05-11 06:45:47

+1

如果你認爲沒有虛析構函數是一個錯誤,那麼你錯了。當然,虛擬析構函數對於派生類來說總是一個好主意......但是,如果您從不使用指向基的指針銷燬派生對象,那麼這不是語言要求。 – 6502 2011-05-11 06:51:28

+0

我編輯了另外一些關於你的C++代碼的評論。 – 6502 2011-05-11 07:28:29