EasyC++16,指針初探之二
大家好,我是梁唐。
想要追求更好閱讀體驗(yàn)的同學(xué),可以點(diǎn)擊文末的「閱讀原文」,訪問(wèn)github倉(cāng)庫(kù)。
危險(xiǎn)的case
指針由于能夠操作內(nèi)存,所以如果使用的時(shí)候不夠仔細(xì),很容易引發(fā)一些意想不到的錯(cuò)誤。
C++ Primer當(dāng)中給了這樣一個(gè)例子:
- int *ptr;
- *ptr = 2333;
在這段代碼當(dāng)中我們聲明了一個(gè)int型的指針,并且將它指向了2333。然而,這里有一個(gè)問(wèn)題,我們?cè)诼暶髦羔樀臅r(shí)候并沒(méi)有進(jìn)行初始化。沒(méi)有初始化的指針并不為空,而是指向一個(gè)未知的地方。如果說(shuō)它指向的是一個(gè)常量1200的地址,我們讓它等于2333,那么之后當(dāng)我們使用1200這個(gè)常量的時(shí)候,得到的結(jié)果都是2333。
更可怕的是,整個(gè)過(guò)程非常地隱蔽,很難察覺(jué)。debug的時(shí)候會(huì)令人抓狂。
所以千萬(wàn)不要修改一個(gè)沒(méi)有初始化的指針指向的值。
指針和數(shù)字
C++ Primer當(dāng)中還給了另外一個(gè)例子,當(dāng)我們輸出指針的時(shí)候,得到的是一串十六進(jìn)制的數(shù)。那我們能不能反過(guò)來(lái)將一個(gè)十六進(jìn)制的數(shù)賦值給指針呢?
- int *p;
- p = 0xB8000000;
答案是不行,因?yàn)轭愋筒灰恢隆km然我們打印指針的時(shí)候看起來(lái)得到是十六進(jìn)制數(shù),但它的類型其實(shí)是指針類型,而不是整數(shù)類型。所以我們將一個(gè)整數(shù)賦值給一個(gè)指針是不行的,如果非要賦值,必須要進(jìn)行類型轉(zhuǎn)換。
- int *p;
- p = (int*) 0xB8000000;
但是這一轉(zhuǎn)換之后顯然又出現(xiàn)了一個(gè)問(wèn)題,我們知道0xB8000000這個(gè)地址指向哪里么?顯然不知道,自然也就說(shuō)不清改了這里的值之后會(huì)引發(fā)什么結(jié)果。
所以雖然這么做可行,但也強(qiáng)烈不建議這樣干。
new操作
前文說(shuō)過(guò)使用指針有一個(gè)非常大的好處就是可以在程序運(yùn)行的時(shí)候,動(dòng)態(tài)分配內(nèi)存。其實(shí)在C語(yǔ)言當(dāng)中也有類似的功能,可以使用malloc來(lái)分配內(nèi)存。不過(guò)在C++當(dāng)中有了更好用的運(yùn)算符——new。
比如我們要?jiǎng)討B(tài)創(chuàng)建一個(gè)int類型的變量,可以這樣寫:
- int *ptr = new int;
new運(yùn)算符根據(jù)之后的類型確定需要的內(nèi)存大小,找到這樣的內(nèi)存之后,返回地址。剛好指針接收的值就是內(nèi)存地址,因此剛好可以完成這樣的賦值操作。
上面的代碼也可以寫成這樣:
- int a;
- int *ptr = &a;
這兩者有什么區(qū)別呢?表面上看沒(méi)有區(qū)別,都是創(chuàng)建了一個(gè)int類型的變量。只不過(guò)第二種寫法除了可以使用指針ptr之外,還可以使用變量名a來(lái)訪問(wèn)這個(gè)int。
但實(shí)際上這兩者的內(nèi)部實(shí)現(xiàn)完全不同,我們直接通過(guò)變量名創(chuàng)建的變量它的值會(huì)被存儲(chǔ)在棧內(nèi)存當(dāng)中,而通過(guò)new創(chuàng)建的對(duì)象則被存儲(chǔ)在堆內(nèi)存當(dāng)中。棧內(nèi)存是由系統(tǒng)自動(dòng)分配,而堆內(nèi)存則是由程序員進(jìn)行申請(qǐng)使用。這兩者的內(nèi)存模型是完全不同的,我們會(huì)在之后的文章詳細(xì)地討論這點(diǎn)。目前簡(jiǎn)單來(lái)理解的話,就是堆內(nèi)存更加靈活,它的空間也更大,可以存儲(chǔ)下更大的數(shù)據(jù)。
delete操作
有了動(dòng)態(tài)創(chuàng)建,自然也就有動(dòng)態(tài)刪除,所以C++當(dāng)中有一個(gè)delete操作和new相對(duì)應(yīng)。
delete運(yùn)算符可以在變量使用結(jié)束之后,將內(nèi)存歸還給內(nèi)存池。因?yàn)楹芏鄷r(shí)候程序當(dāng)中的變量都是一次性使用或者是有生命周期的,當(dāng)生命周期結(jié)束,使命完成就沒(méi)有必要繼續(xù)占用著資源了。畢竟系統(tǒng)內(nèi)的內(nèi)存資源是有限的,尤其是在一些大型項(xiàng)目或者嵌入式系統(tǒng)當(dāng)中,內(nèi)存資源非常緊張。
delete運(yùn)算符之后跟一個(gè)指針,它會(huì)釋放改指針指向的內(nèi)存。
- int *ptr = new int;
- delete ptr;
這里面有很多坑,千萬(wàn)要當(dāng)心。首先是使用了new創(chuàng)建了內(nèi)存之后,一定要記得delete,否則這塊內(nèi)存將會(huì)永遠(yuǎn)被占用無(wú)法得到釋放,這種情況被稱為內(nèi)存泄漏(memory leak)。另外,我們不能delete一個(gè)已經(jīng)delete過(guò)的指針,這也會(huì)引發(fā)嚴(yán)重錯(cuò)誤。C++ Primer對(duì)此的描述是:什么情況都可能發(fā)生。當(dāng)然也不能再使用一個(gè)已經(jīng)被delete的指針,這會(huì)引發(fā)空指針錯(cuò)誤。
指針對(duì)于C++來(lái)說(shuō)是一把雙刃劍,像是Java、Python、Go等其他語(yǔ)言,內(nèi)存回收的工作都是由系統(tǒng)自動(dòng)執(zhí)行的。例如Java的JVM虛擬機(jī)設(shè)計(jì)了嚴(yán)密的GC(垃圾回收)機(jī)制,程序員無(wú)須關(guān)心內(nèi)存的回收問(wèn)題,全部交給程序自動(dòng)完成。
而在C++當(dāng)中,這一過(guò)程是由程序員手動(dòng)執(zhí)行的,某種程度上來(lái)說(shuō),這當(dāng)然非常好,程序員擁有了很高的權(quán)限以及靈活度。但同樣也是一個(gè)坑,尤其是在復(fù)雜系統(tǒng)當(dāng)中,很難準(zhǔn)確判斷delete執(zhí)行的時(shí)間。這會(huì)引發(fā)嚴(yán)重的問(wèn)題,例如內(nèi)存泄漏嚴(yán)重,野指針到處飛等……