一、莫名的恐懼感
對於指針確實有種莫名的恐懼感,從剛開始學習的時候就被老師灌輸的概念是指針功能很強大,但是用得不夠好,會有很大的副作用。什麼叫用得夠好?初學者誰都不會用,那且不是都不能用了?如果都這樣,指針都沒人用了,還要指針做什麼?
陷入了上述困局的原因在於我們的這種莫名的恐懼感,指針如此之神秘,以至於如我一樣大多數的人都望而生畏,無所適從,被迫放棄。
二、野指針
誠如當初老師的忠告一樣, 指針是個很強大的工具,可是正因為它太強大,所以要操作它不是件易事。操作不當造成的野指針,甚至會引起系統死機等比較嚴重的後果。
如果程序定義了一個指針,就必須要立即讓它指向一個我們設定的空間或者把它設為NULL,如果沒有這麼做,那麼這個指針裡的內容是不可預知的,即不知道它指向內存中的哪個空間(即野指針),它有可能指向的是一個空白的內存區域,可能指向的是已經受保護的區域,甚至可能指向系統的關鍵內存,如果是那樣就糟了,也許我們後面不小心對指針進行操作就有可能讓系統出現紊亂,死機了。所以我們必須設定一個空間讓指針指向它,或者把指針設為NULL,這是怎麼樣的一個原理呢,如果是建立一個與指針相同類型的空間,實際上是在內存中的空白區域中開辟了這麼一個受保護的內存空間,然後用指針來指向它,那麼指針裡的地址就是這個受保護空間的地址了,而不是不可預知的啦,然後我們就可以通過指針對這個空間進行相應的操作了;如果我們把指針設為NULL,我們在頭文件定義中的 #define NULL 0 可以知道,其實NULL就是表示0,那麼我們讓指針=NULL,實際上就是讓指針=0,如此,指針裡的地址(機器數)就被初始化為0了,而內存中地址為0 的內存空間……不用多說也能想象吧,這個地址是特定的,那麼也就不是不可預知的在內存中亂指一氣的野指針了。
還應該注意的是,free和delete只是把指針所指的內存給釋放掉,但並沒有把指針本身干掉。指針p被free以後其地址仍然不變(非NULL),只是該地址對應的內存是垃圾,p成了“野指針”。如果此時不把p設置為NULL,會讓人誤以為p是個合法的指針。用free或delete釋放了內存之後,就應立即將指針設置為NULL,防止產生“野指針”。內存被釋放了,並不表示指針會消亡或者成了NULL指針。(而且,指針消亡了,也並不表示它所指的內存會被自動釋放。)
三 例說野指針
例子1:
首先請諸位看以下一段“危險”的C++代碼:
void function( void )
{
char* str = new char[100];
delete[] str;
// Do something
strcpy( str, "Dangerous!!" );
}
之所以說其危險,是因為這是一段完全合乎語法的代碼,編譯的時候完美得一點錯誤也不會有,然而當運行到strcpy一句的時候,問題就會出現,因為在這之前,str的空間已經被delete掉了,所以strcpy當然不會成功。對於這種類似的情況,在林銳博士的書中有過介紹,稱其為“野指針”。
例子2:——指針初始化引出的問題
對指針初始化時,引出的應該注意的問題小結:
(1)先看例子:
#include <iostream.h>
void main()
{
char *p,*p1="hello first!";
while((*(p++) = *(p1++)) != 0);
cout<<p<<endl;
}
錯處:
p定義時沒有初始化,p是指向不定,是一個野指針。
p++可能引用得空間為非法的。
編譯時不會出錯,但運行時會造成程序崩潰。
(2)把上面的p初始化為NULL
#include <iostream.h>
void main()
{
char *p=NULL,*p1="hello first!";
while((*(p++) = *(p1++)) != 0);
cout<<p<<endl;
}
也錯:
NULL表示沒有任何指向。p++沒有任何意義,運行時會造成程序崩潰。這裡就應該想到不能對NULL的指針進行直接操作。
(3)現在為p初始化為" ":
void main()
{
char *p=" ",*p1="hello first!";
while((*(p++) = *(p1++)) != 0);
cout<<p<<endl;
}
還錯:
p指向的是一個const常量的內容,可以通過*(p++)形式引用該值,但是不能改變它指向const的內容。
(4)
#include <iostream.h>
#include <string.h>
void main()
{
char c[]="";
char *p=c,*p1="hello first!";
char *p_start=p;
while((*(p++) = *(p1++)) != 0);
cout<<c<<endl;
}
問題:
此時數組是一系列的變量了,也就是p一有指向,二不是指向常量的而是指向變量的。所以按理應該行的。問題出在c太小,造成了數組越界,所以錯掉!把c的大小改來不比"hello first!"小就行了。
5)對於的就想到用new來初始化了:
#include <iostream.h>
#include <string.h>
void main()
{
char *p,*p1="hello first!";
p=new char;
char *p_start=p;
while((*(p++) = *(p1++)) != 0);
cout<<p_start<<endl;
}
現在就可以了,哈,不過,我認為在new時最好還是把它的大小給指出來,如new char[strlen(p1)+1];如果不指定大小,我想p++會指向一些已用得地址,而這些地址又不能隨便改,就會造成諸如野指針樣程序崩潰了。
小結:對於前面的問題,不防這樣來寫:
#include <iostream.h>
#include <string.h>
void main()
{
char *p=NULL,*p1="hello first!";
p=new char[strlen(p1)+1];
//p=new char; //覺得最好別這樣子,new char只相當於得到一個char對
char *p_start=p; //象,分配一個字符的空間。
while((*(p++) = *(p1++)) != 0);
cout<<p_start<<endl;
}
四 造成野指針的原因
1、指針變量沒有被初始化。任何指針變量剛被創建時不會自動成為NULL指針,它的默認值是隨機的,它會亂指一氣。
2、指針p被free或者delete之後,沒有置為NULL,讓人誤以為p是個合法的指針。
3、指針操作超越了變量的作用范圍。這種情況讓人防不勝防。
五 正確使用指針
通常避免野指針的辦法是正確的使用指針
1.聲明一個pointer的時候注意初始化為null :
int* pInt = NULL;
2.分配完內存以後注意ASSERT:
pInt = new int[num];
ASSERT(pInt != NULL);
3. 刪除時候注意用對操作符:
對於new int類型的,用delete
對於new int[]類型的,用delete []
4.刪除完畢以後記得給他null地址:
delete [] pInt;
pInt = NULL;
5.記住,誰分配的誰回收,不要再一個函數裡面分配local pointer,送到另外一個函數去delete。
6.返回local address是非常危險的,如必須這樣做,請寫注釋到程序裡面,免得忘記。