C-關(guān)于指針

?

c和指針的關(guān)系十分密切,所以在本文,我們會(huì)詳細(xì)的談?wù)勚羔?。這邊我會(huì)結(jié)合<<c與指針>>這本書(shū)的內(nèi)容來(lái)介紹它。

一.內(nèi)存與地址

計(jì)算機(jī)的內(nèi)存可以看作是一條長(zhǎng)街上的一排房屋。每座房子都可以容納數(shù)據(jù),并通過(guò)一個(gè)房號(hào)來(lái)標(biāo)識(shí)。這個(gè)比喻頗為有用,但也存在局限性。計(jì)算機(jī)的內(nèi)存由數(shù)以?xún)|萬(wàn)計(jì)的位(bit)組成,每個(gè)位可以容納值0或1。由于一個(gè)位所能表示的范圍太有限,所以單獨(dú)的位用處不大,通常許多位組成一個(gè)單位,這樣就可以存儲(chǔ)范圍較大的值。這里有一幅圖,展示了現(xiàn)實(shí)機(jī)器的一些內(nèi)存情況。

?

這里每個(gè)位置的方塊都被稱(chēng)為字節(jié)(byte),每個(gè)字節(jié)都包含了存儲(chǔ)一個(gè)字符所需要的位數(shù),在許多現(xiàn)代機(jī)器上,每個(gè)字節(jié)包含8個(gè)位,可以表示無(wú)符號(hào)值0~255,或有符號(hào)值-128~127,上面這張圖沒(méi)有顯示出這部分內(nèi)容,但內(nèi)存中的每個(gè)位置總是包含著一些值。每個(gè)字節(jié)通過(guò)地址來(lái)標(biāo)識(shí),如上圖方框上面的數(shù)字所示。

為了存儲(chǔ)更大的值,我們把兩個(gè)或多個(gè)字節(jié)合在一起作為一個(gè)更大的存儲(chǔ)單位。例如,許多機(jī)器以字位單位存儲(chǔ),每個(gè)字通常由2個(gè)或者4個(gè)字節(jié)組成。下面這張圖所標(biāo)識(shí)的內(nèi)存位置與上圖相同,但這次它以4個(gè)字節(jié)的字來(lái)表示。

?

由于它們包含了更多的位,每個(gè)字可以容納的無(wú)符號(hào)整數(shù)的范圍更大了(0~4294967295(2^32-1))。

注意,盡管一個(gè)字包含了4個(gè)字節(jié),它仍然只有一個(gè)地址。至于它的地址是從最左邊那個(gè)字節(jié)的位置還是最右邊字節(jié)的位置開(kāi)始,不同的機(jī)器有不同的規(guī)定(大小端)。另一個(gè)需要注意的是邊界對(duì)齊(boundary alignment),在要求邊界對(duì)齊的機(jī)器上,整型值存儲(chǔ)的起始位置只能是某些特定的字節(jié),通常是2或4的倍數(shù)。但這些問(wèn)題是硬件設(shè)計(jì)師的事情,它們很少影響寫(xiě)程序的,廢話(huà)有點(diǎn)多了,總之,我們通常只對(duì)兩件事情感興趣:

內(nèi)存中的每個(gè)位置都有一個(gè)獨(dú)一無(wú)二的地址標(biāo)識(shí)

內(nèi)存中的每個(gè)位置都包含一個(gè)值

地址與內(nèi)容,

這里有另外一個(gè)例子,這次它顯示了內(nèi)存中5個(gè)字的內(nèi)容。

?

這里顯示了5個(gè)整數(shù),每個(gè)都位于自己的字中。如果你記住了一個(gè)值的存儲(chǔ)地址,你以后可以根據(jù)這個(gè)地址取得這個(gè)值。

但是要記住所有這些地址可太笨拙了,所以高級(jí)語(yǔ)言提供的特性之一就是通過(guò)名字(變量名)而不是地址來(lái)訪(fǎng)問(wèn)內(nèi)存的位置。下面這張圖與上圖相同,但這次使用名字來(lái)代替。

?

當(dāng)然,這里的名字就是我們所稱(chēng)的變量。有一點(diǎn)非常重要,你必須記住,名字與內(nèi)存位置之間的關(guān)聯(lián)不是硬件所提供的,它是由編譯器為我們實(shí)現(xiàn)的。所以這些變量其實(shí)是給了我們一種更方便的方法去記住地址,實(shí)際上硬件仍然可以通過(guò)地址訪(fǎng)問(wèn)內(nèi)存位置。

二、值與類(lèi)型

現(xiàn)在我們看下上圖存儲(chǔ)于這些位置的值。頭兩個(gè)位置存儲(chǔ)的是整數(shù)112和-1。第三個(gè)位置存儲(chǔ)的是一個(gè)非常大的整數(shù),第四五個(gè)位置存的也是整數(shù)。下面是這些變量的聲明:

?

在這些聲明中,變量a和b確實(shí)用于存儲(chǔ)整型值。但是c所存儲(chǔ)的是浮點(diǎn)值。但上圖中c的值卻是一個(gè)整數(shù)。那么c到底是整數(shù)還是浮點(diǎn)數(shù)呢?

答案是該變量包含了一序列內(nèi)容為0或者1的位。這個(gè)01序列可以被解釋為整數(shù),也可以被解釋為浮點(diǎn)數(shù),這取決于它們被使用的方式。如果使用的是整型算術(shù)指令,這個(gè)值就是整數(shù),如果用的是浮點(diǎn)指令,它就是個(gè)浮點(diǎn)數(shù)。

這個(gè)事實(shí)引出了一個(gè)重要的結(jié)論:不能簡(jiǎn)單地通過(guò)檢查一個(gè)值的位來(lái)判斷它的類(lèi)型,為了判斷值的類(lèi)型(以及它所表達(dá)的值),你必須觀(guān)察程序中這個(gè)值的使用方式??紤]下面這個(gè)以二進(jìn)制01表示的32位值:

?

下面是這些位可能被解釋的許多情況中的幾種。這些值都是從一個(gè)基于Motorola68000的處理器得到的。如果換個(gè)系統(tǒng),使用不同的數(shù)據(jù)格式和指令,對(duì)這些位的解釋將有所不同。

?

這里,一個(gè)單一的值可以被解釋為5種不同的類(lèi)型。顯然,值的類(lèi)型并非值本身所固有的一種特性,而是取決于它的解析方式。因此,為了得到正確答案,對(duì)值進(jìn)行正確的使用是非常重要的。

當(dāng)然,編譯器會(huì)幫助我們避免這些錯(cuò)誤。如果我們把變量c聲明為float型變量,那么當(dāng)程序訪(fǎng)問(wèn)它時(shí),編譯器就會(huì)產(chǎn)生浮點(diǎn)型指令。如果我們以某種對(duì)float類(lèi)型而言不適當(dāng)?shù)姆绞皆L(fǎng)問(wèn)該變量時(shí),編譯器就會(huì)發(fā)出錯(cuò)誤或者警告信息?,F(xiàn)在看來(lái)非常明顯,圖中所標(biāo)明的值是具有誤導(dǎo)性質(zhì)的,因?yàn)樗@示了c的整型表示方式,事實(shí)上真正的浮點(diǎn)值是3.14。

三、指針變量的內(nèi)容

話(huà)題轉(zhuǎn)回指針,看看變量d和e的聲明。它們被聲明位指針,并用其他變量的地址予以初始化。指針的初始化是用&(用在這邊稱(chēng)為取地址符)操作符完成的,它用于產(chǎn)生操作數(shù)的內(nèi)存地址。

?

d和e的內(nèi)容是地址而不是整型或浮點(diǎn)型數(shù)值。事實(shí)上,結(jié)合上面幾幅圖可以容易地看出,d的內(nèi)容和a的存儲(chǔ)地址一致,而e的內(nèi)容與c的存儲(chǔ)一致,這也正是我們對(duì)這兩個(gè)指針進(jìn)行初始化時(shí)所期望的結(jié)果。區(qū)分變量d的地址(112)和它所存儲(chǔ)的內(nèi)容(100)是非常重要的,同時(shí)也必須意識(shí)到100這個(gè)數(shù)值用于標(biāo)識(shí)其他位置(是某個(gè)內(nèi)存位置的地址)。在這一點(diǎn)上,本文一開(kāi)始房屋這個(gè)比喻不是很行得通,因?yàn)榉孔拥膬?nèi)容不太可能是其他房子的地址。

在下一步之前,先看一下涉及這些變量的表達(dá)式。仔細(xì)考慮這些聲明,?

a、b、c、d、e的值分別是什么呢?

前3個(gè)很顯然:a的值是112,b的值是-1,c的值是3.14。指針變量其實(shí)也很容易,d的值是100,e的值是108。如果你認(rèn)為d和e的值分別是112和3.14,那么你就犯了一個(gè)極為常見(jiàn)的錯(cuò)誤。d和e被聲明為指針并不會(huì)改變這些表達(dá)式的求值方式:一個(gè)變量的值就是分配給這個(gè)變量的內(nèi)存位置所存儲(chǔ)的數(shù)值。如果你簡(jiǎn)單地認(rèn)為由于d和e是指針,所以它們可以自動(dòng)獲得存儲(chǔ)于位置100和108的值,那你就錯(cuò)了。變量的值就是分配給該變量的內(nèi)存位置所存儲(chǔ)的數(shù)值,即使是指針變量這點(diǎn)也不會(huì)變。

四、間接訪(fǎng)問(wèn)操作符

通過(guò)一個(gè)指針訪(fǎng)問(wèn)它所指向的地址的過(guò)程稱(chēng)為間接訪(fǎng)問(wèn)(indirection)或解引用指針(dereferencing pointer)。這個(gè)用于執(zhí)行間接訪(fǎng)問(wèn)的操作符是單目操作符*。這里有一些例子,它們使用了前面小節(jié)里的一些聲明。

?

d的值是100,當(dāng)我們對(duì)d使用間接訪(fǎng)問(wèn)操作符時(shí),它表示訪(fǎng)問(wèn)內(nèi)存位置100并查看那里的值。因此,*d的右值是112,即位置100的內(nèi)容,它的左值是位置100本身。

注意上表各個(gè)表達(dá)式的類(lèi)型:d是一個(gè)指向整型的指針,對(duì)它進(jìn)行解引用操作將產(chǎn)生一個(gè)整型值。類(lèi)似,對(duì)float*進(jìn)行間接訪(fǎng)問(wèn)將產(chǎn)生一個(gè)float型的值。

正常情況下,我們并不知道編譯器為每個(gè)變量所選擇的存儲(chǔ)位置,所以我們實(shí)現(xiàn)無(wú)法預(yù)測(cè)它們的地址。這樣,當(dāng)我們繪制內(nèi)存中的指針圖時(shí),用實(shí)際數(shù)值表示地址是不方便的。所以絕大部分書(shū)籍改用箭頭代替,如下所示:

?

但是,這種記法可能會(huì)引起誤解,因?yàn)榧^可以使你誤以為執(zhí)行了間接訪(fǎng)問(wèn)操作,但事實(shí)上,它不一定會(huì)執(zhí)行這個(gè)操作。例如根據(jù)上圖,你能推斷表達(dá)式d的值是什么?

如果你的答案是112,那么你就被這個(gè)箭頭誤導(dǎo)了。正確答案是a的地址,而不是它的內(nèi)容。這個(gè)箭頭似乎會(huì)把你的注意力吸引到a上。要使你的思維不受箭頭影響是不容易的,這也是問(wèn)題所在:除非存在間接引用操作符,否側(cè)不要被箭頭所誤導(dǎo)。

五、未初始化和非法的指針

下面這個(gè)代碼段說(shuō)明了一個(gè)極為常見(jiàn)的錯(cuò)誤:

?

這個(gè)聲明創(chuàng)建了一個(gè)名為a的指針變量,后面那條賦值語(yǔ)句把12存儲(chǔ)在a所指向的內(nèi)存位置。

警告:

但是a究竟指向哪里呢?我們聲明了這個(gè)變量,但從未對(duì)它進(jìn)行初始化,所以我們沒(méi)有辦法預(yù)測(cè)12這個(gè)值將存儲(chǔ)于聲明地方,從這一點(diǎn)來(lái)看,指針變量和其他變量并無(wú)區(qū)別。如果變量是靜態(tài)的,則它會(huì)被初始化為0;如果變量是自動(dòng)的,它根本不會(huì)被初始化。無(wú)論是哪種情況,聲明一個(gè)整型的指針都不會(huì)“創(chuàng)建”(確切來(lái)說(shuō)是分配)用于存儲(chǔ)整型值的內(nèi)存空間。

所以,如果程序執(zhí)行這個(gè)賦值操作,會(huì)發(fā)生什么情況呢?如果你運(yùn)氣好,a的初始值會(huì)是一個(gè)非法地址,這樣賦值語(yǔ)句將會(huì)出錯(cuò),從而終止程序。在UNIX系統(tǒng)上,這個(gè)錯(cuò)誤被稱(chēng)為“段違例(segmention violation)”或“內(nèi)存錯(cuò)誤(memory fault)”。它提示程序試圖訪(fǎng)問(wèn)一個(gè)并未分配給該程序的內(nèi)存位置。在一臺(tái)運(yùn)行Windows的PC上,對(duì)未初始化或非法指針進(jìn)行間接訪(fǎng)問(wèn)操作是一般保護(hù)性異常(General Protection Exception)的根源之一。

對(duì)于那些要求整數(shù)必須存儲(chǔ)于特定邊界的機(jī)器而言,如果這種類(lèi)型的數(shù)據(jù)在內(nèi)存中的存儲(chǔ)地址在錯(cuò)誤的邊界上,那么對(duì)于這個(gè)地址進(jìn)行訪(fǎng)問(wèn)時(shí)將會(huì)產(chǎn)生一個(gè)錯(cuò)誤。這種錯(cuò)誤在UNIX系統(tǒng)中被稱(chēng)為“總線(xiàn)錯(cuò)誤(bus error)”。

一個(gè)更為嚴(yán)重的情況是:這個(gè)指針偶爾可能包含一個(gè)合法的地址。接下來(lái)的事很簡(jiǎn)單:

位于那個(gè)位置的值被修改,雖然你無(wú)意去修改它,像這種類(lèi)型的錯(cuò)誤非常難以捉摸,因?yàn)橐l(fā)錯(cuò)誤的代碼可能與原先用于操作那個(gè)值的代碼完全不相干,所以在你對(duì)指針進(jìn)行間接訪(fǎng)問(wèn)之前,必須非常小心,確保它們已被初始化。

未完待續(xù)...

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 指針是C語(yǔ)言中廣泛使用的一種數(shù)據(jù)類(lèi)型。 運(yùn)用指針編程是C語(yǔ)言最主要的風(fēng)格之一。利用指針變量可以表示各種數(shù)據(jù)結(jié)構(gòu); ...
    朱森閱讀 3,613評(píng)論 3 44
  • C語(yǔ)言是面向過(guò)程的,而C++是面向?qū)ο蟮?C和C++的區(qū)別: C是一個(gè)結(jié)構(gòu)化語(yǔ)言,它的重點(diǎn)在于算法和數(shù)據(jù)結(jié)構(gòu)。C程...
    小辰帶你看世界閱讀 1,021評(píng)論 0 6
  • 第十章 指針 1. 地址指針的基本概念: 在計(jì)算機(jī)中,所有的數(shù)據(jù)都是存放在存儲(chǔ)器中的。一般把存儲(chǔ)器中的一個(gè)字節(jié)稱(chēng)為...
    堅(jiān)持到底v2閱讀 1,166評(píng)論 2 3
  • 指針 在了解什么是指針之前,我們需要先搞清楚數(shù)據(jù)在內(nèi)存中是如何存儲(chǔ)的,又是如何讀取的。 如果在程序中定義一個(gè)變量,...
    Longshihua閱讀 1,143評(píng)論 0 4
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類(lèi)型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,649評(píng)論 1 32

友情鏈接更多精彩內(nèi)容