懸掛指標

weixin_33831673發表於2013-06-21

指標懸掛

指標是C/C++語言中一種特殊的資料型別,它的值是一塊記憶體區域的地址。使用指標要求,它的值必須是指向一塊分配給你使用的地址,且使用的記憶體不能超過它分配時的大小。例如:

 char * p = new char[10];

這樣的程式碼就給p分配一塊有10個位元組的記憶體,並把這塊記憶體的開始地址放在p中。使用者在使用時,必須保證引用的記憶體必須在以p開始到這塊記憶體結束的範圍內。

閒話少敘,說說指標懸掛。所謂指標懸掛是指指標指向了一塊沒有分配給使用者使用的記憶體。指標懸掛一般由以下幾種情況:

指標未初始化

這不僅僅是初學者才會犯的錯誤。尤其是全域性指標變數,不初始化就使用的情況很正常,考慮如下程式碼:

 

 char * g_pBuffer ;

 int    g_nSize;

 void InitBuffer(int size)

 {

 

  g_nSize = size;

  g_pBuffer = new char[size];

 

 }

 

 void DumpBuffer()

 {

 

  CFile file;

  file.Open(filename,CFile::modeWrite);

  file.WriteHuge(g_pBuffer,g_nSize);

 

 }

 

這段程式碼如果InitBuffer只被呼叫一次且在DumpBUffer之前被呼叫自然沒有問題,問題是,對於大專案中,這種函式呼叫的先後關係往往是很複雜的,這樣就無法保證InitBuffer被先呼叫,在此函式被呼叫之前,這個指標就是一個懸掛著的指標。良好的程式設計習慣是:首先給指標初始化為一個0值(注意我這裡沒有用空值,因為空值這個術語含義不明),然後在使用的時候檢查這個指標,修改後的程式碼如下:

 

 char * g_pBuffer =(char*)0;

 int    g_nSize   =0;

 void InitBuffer(int size)

 {

 

  if((char*)0 != g_pBuffer)

  {

 

   delete [] g_pBuffer;

 

  }

  g_nSize = size;

  g_pBuffer = new char[size];

 

 }

 

 void DumpBuffer()

 {

 

  if((char*)0 == g_pBuffer)

  {

 

   InitBuffer(100);

 

  }

  CFile file;

  file.Open(filename,CFile::modeWrite);

  file.WriteHuge(g_pBuffer,g_nSize);

 

 }

 

指標拷貝後刪除了指標

如果在使用指標過程中對指標進行了拷貝,然後其中一個拷貝被刪除,則另外一個拷貝就成了懸掛指標,如下程式碼就是一個例子:

 

 char * g_pBuffer =(char*)0;

 int    g_nSize   =0;

 void InitBuffer(int size,char * pBuffer)

 {

 

  if((char*)0 != g_pBuffer)

  {

 

   delete [] g_pBuffer;

 

  }

  g_nSize = size;

  g_pBuffer = pBuffer;

 

 }

 

 void DumpBuffer()

 {

 

  if((char*)0 == g_pBuffer)

  {

 

   InitBuffer(100);

 

  }

  CFile file;

  file.Open(filename,CFile::modeWrite);

  file.WriteHuge(g_pBuffer,g_nSize);

 

 }

 

 void UseBuffer()

 {

 

  char * pBuffer = new char[100];

  InitBuffer(100,pBuffer);

  delete []pBuffer;

  DumpBuffer();

 

 }

 

 

關注藍色程式碼,在InitBuffer之後,pBuffer立刻被刪除,而此時g_pBuffer還儲存這個指標的一個備份,這個備份就成為一個

懸掛的指標。因此在儲存備份的時候一定要小心,否則就會出問題。

 

類和結構中的指標懸掛

 

在類和結構中的指標則更容易出危險。我們假設要設計一個字串類如下:

class CMyString

{

protected:

 

 int m_nSize;

 char* m_pData;

 

public:

 

 CMyString(const char * pStr);

 virtual ~CMyString();

 

};

CMyString::CMyString(const char * pstr)

{

 

 m_nSize = strlen(pstr);

 m_pData = new char[m_nSize +1];

 strcpy(m_pData,pstr);

 

}

CMyString::~CMyString()

{

 

 delete[]m_pData

 

}

 

這段程式碼似乎沒有問題,實際上隱含了很嚴重的問題。考慮下面這段程式碼:

CMyString GetString()

{

 

 

  1. 1.   CMyString str1 = "haha";
  2. 2.   
  3. 3.   CMyString str2 = "xixi";
  4. 4.   
  5. 5.   CMyString str3 = str1;
  6. 6.   
  7. 7.   str2 = str1;
  8. 8.   

 

 return str3;

 

}

 

上述程式碼中,1、2行分別初始化了一個CMyString物件,第三行則使用str1來初始化str3,第四行則使用等於號賦值。下面對於

第三行和第四行分別說明問題。

 

拷貝建構函式問題

 

對於第三行,系統呼叫CMyString的拷貝建構函式來初始化str3,其呼叫格式等價於

 

CMyString str3(str1);

 

由於CMyString沒有提供拷貝建構函式,編譯器會把str1的內容原原本本的複製給str3,這樣,str1.m_pData這個指標也

被複制給str3了。在函式退出時,str1首先析構(順序和編譯器有關,這裡只是一個假設,其實誰先析構問題都一樣),

其解構函式刪除了m_pData,此時str3的m_pData就成為一個懸掛指標。當str3析構時,它試圖刪除m_pData必然造成一個異常。

 

對於GetString函式本身而言,它返回了str3物件,返回過程會建立一個CMyString臨時物件,並用str3作為引數呼叫拷貝構造

函式,其結果和前面所述一樣。

 

operator=問題

 

在第四行,程式使用=運算子給str2賦值。由於類沒有提供operator =,編譯器預設實現是把str1的內容完全複製給str2,

這樣導致的後果和前面是一樣的。

 

避免指標懸掛的要點

 

 

  1. 不儲存指標的拷貝,如果要儲存指標指向的內容,則新分配一塊大小相等的記憶體,把其內容完全拷貝過去。如果確實不得不
  2. 儲存拷貝,必須小心關注每個拷貝的使用情況,一旦一個拷貝進行了刪除,則其他拷貝必須立刻放棄使用。
  3.  
  4.  
  5.  
  6.  
  7.  
  8.  
  9.  

10.包含指標的結構和類必須實現拷貝建構函式和operator=運算子,如果不願意實現,對其賦值和構造必須

11.針對每個成員變數進行特殊處理。

13.如果結構或者類的成員變數直接或者間接的包含指標,也必須如前一條處理(例如結構中定義了string成員)

15.指標使用完畢並刪除後,應該把指標變數的值設定為0。

 

相關文章