c++野指针小结
1.什么是野指針
所謂野指針(wild pointer),簡單講是指指向不可用的內存區域的指針。需要注意的一點是,野指針與NULL空指針是不同的。NULL指針一般比較好判斷,直接用if (p==NULL)語句判斷即可。但是野指針指向的是垃圾內存區域的指針,一旦使用往往會造成不可預測的結果,這種隨機不可預測的結果才是最可怕的。
2.未初始化
造成野指針最常見的情況之一就是指針未被正確初始化。任何指針在被創建的時候,不會自動變成NULL指針,他的default值是隨機的。所以一個比較好的習慣是,指針剛創建的時候,要么設置為NULL空指針,要么指向合理的內存區域。
看一下一個簡單例子
void func() {int* p;cout<<*p<<endl; }我們在main方法中調用該函數,會得到輸出
1901647869不難看出得到的就是一個隨機值。
3.懸垂指針
懸垂指針也是野指針常見的一種。當我們顯式地從內存中刪除一個對象或者返回時通過銷毀棧幀,并不會改變相關的指針的值。這個指針實際仍然指向內存中相同位置,甚至該位置仍然可以被讀寫,只不過這時候該內存區域完全不可控,因為你不能保證這塊內存是不是被別的程序或者代碼使用過。
void dangling_point() {char *p = (char *)malloc(10);strcpy(p, "abc");cout<<p<<endl;free(p);if (p != NULL) cout<<p<<endl;strcpy(p, "def"); }我們執行free§語句時,p所指向的內存被釋放,此時該內存區域變成不可用內存。但是,p此時指向的地址并沒有發生改變,而且也不會為空,所以if(p != NULL)判斷為真,會繼續執行后面的語句。
但是strcpy(p, “def”); 這一句,雖然代碼不會報錯崩潰,但此時正在篡改動態內存區,會產生不可預料的結果,此操作相當危險,尤其是debug時候非常不好排查,往往會讓人崩潰…
為了避免懸垂指針的問題,一般做法是在free/delete指針以后,再將p=NULL,這樣就可以避免上述問題。
4.返回棧內存指針或引用
看一個例子
int* return_ret() {int result = 1;return &result; }int main(int argc, char const *argv[]) {int *p = return_ret();cout<<*p<<endl;cout<<*p<<endl;return 0; }首先,這段代碼能編譯通過。不過IDE會提醒warning:
test_code.cc:80:13: warning: address of stack memory associated with local variable 'result' returned [-Wreturn-stack-address]return &result;^~~~~~ 1 warning generated.什么意思?就是我們返回了一個局部變量的棧內內存地址。
代碼也能正常運行:
我們注意看,第一個打印語句,甚至還能輸出"正確"的結果1。
但是到了第二個打印語句,此時輸出的結果就不可控了,因為此時該內存區域的內容,已經發生了變化。此時該內存地址已經變得不"可靠",操作該內存區域將會相當危險。
5.特殊的空指針
空指針為一特殊指針,是唯一一個對任何指針類型都合法的指針值。為了提高程序可讀性,標準庫定義了一個與0等價的符號常量NULL。p = 0;p = NULL;都是把p置為空指針值,上面兩種寫法等價。
6.free操作的真相
以下部分內容來自網絡:
內存管理有以下幾個層次(從高到低):C程序 - C庫(malloc)- 操作系統 - 物理內存
首先,操作系統保證每個進程都有獨立的虛擬內存空間(32bit上應該是4G吧,一般進程也用不了這么多)。當然實際上物理內存是所有進程共享的,所以當你需要動態內存時,需要向操作系統申請,這時候雖然從你程序的角度,內存是連續的,其實是被操作系統映射到某一塊物理內存而已。程序用完內存歸還后,實際歸還的部分可能被操作系統分配給其他進程。
要注意,上面說的“歸還”是malloc庫的行為。malloc庫會使用一些策略來提高內存使用的效率,比如程序需要使用10K內存時,malloc實際可能上會申請1M,因為一次系統調用開銷很大;再比如即使你調用了free“歸還“了程序使用的內存,malloc庫也可能并未真正把這些內存歸還給操作系統,因為將來程序可能還會再申請動態內存。
malloc庫有多種實現,我知道的一種是使用標記(tag)來存儲內存的元信息。比如你申請了8個byte,得到的頭指針地址是0x1001(實際內存為0x1001-0x1008),malloc會在0x1000(也就是頭指針-1的位置)保存8,即這段內存的長度。等釋放時,程序將頭指針地址傳給free,malloc庫從頭指針-1的位置發現需要釋放的內存長度,釋放內存(實際的操作可能只是將tag清空)。這就解釋了:1. 為什么和malloc不同,free的參數只有一個頭指針而不需要長度;2. free后內存實際上可能并未歸還給操作系統。
所以,訪問被(程序)釋放的內存是一種undefined行為,就是說結果是不確定的。在malloc庫未將此內存歸還給操作系統也未進行下一次動態分配時,這塊內存事實上仍屬于程序。而當malloc庫不清理歸還的內存時(多數實現都是如此),你能訪問到的值仍是原來的值。這和函數調用完畢而未清理棧幀、后續調用函數可以訪問到之前已經設置的局部變量值是一個道理。
但是,當malloc庫已經將內存歸還給系統時,再去訪問原來的地址(更別說寫),由于這段地址已經不屬于程序了,就會出現經典的segmentation fault。
說到底,這些現象還是C語言庫為了更有效率的實現而妥協的結果。
總結
- 上一篇: nc后门
- 下一篇: Vue+Element-UI 上传图片,