指標懸掛
指標是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. CMyString str1 = "haha";
- 2.
- 3. CMyString str2 = "xixi";
- 4.
- 5. CMyString str3 = str1;
- 6.
- 7. str2 = str1;
- 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,
這樣導致的後果和前面是一樣的。
避免指標懸掛的要點
- 不儲存指標的拷貝,如果要儲存指標指向的內容,則新分配一塊大小相等的記憶體,把其內容完全拷貝過去。如果確實不得不
- 儲存拷貝,必須小心關注每個拷貝的使用情況,一旦一個拷貝進行了刪除,則其他拷貝必須立刻放棄使用。
10.包含指標的結構和類必須實現拷貝建構函式和operator=運算子,如果不願意實現,對其賦值和構造必須
11.針對每個成員變數進行特殊處理。
13.如果結構或者類的成員變數直接或者間接的包含指標,也必須如前一條處理(例如結構中定義了string成員)
15.指標使用完畢並刪除後,應該把指標變數的值設定為0。