周立功:安全有效地使用C掌握指針—變量的訪問 2017-07-05 電子工程師時(shí)間 電子工程師時(shí)間 ee-technotes 分享電子工程相關(guān)技術(shù)文檔:設(shè)計(jì)技巧,應(yīng)用筆記,參考設(shè)計(jì),設(shè)計(jì)方案 經(jīng)周立功教授授權(quán),特對(duì)本書內(nèi)容進(jìn)行連載。第一章為程序設(shè)計(jì)基礎(chǔ),本文為1.3.2節(jié)變量的訪問。 周立功 1. 間接訪問指針變量與普通變量沒有任何區(qū)別,由于通過變量名即可獲取變量的值,因此指針變量的值就是它存儲(chǔ)的內(nèi)存地址。如果想要獲取內(nèi)存地址中存儲(chǔ)的值,就必須使用特殊的語法“*”。假設(shè)0x22FF70存儲(chǔ)單元中保存的是ptr的值0x22FF74,則通過ptr就可以找到iNum的值0x64,詳見圖1.7。每當(dāng)指針的指向改變時(shí),便繪制新的箭頭;每當(dāng)變量的值發(fā)生改變時(shí),更新它的值。通過這些操作,即使再復(fù)雜的系統(tǒng),也能夠理解了。顯然指針變量對(duì)所指向的變量的訪問,自然也就成了對(duì)變量的“間接訪問”。即: * 指針變量 即間接引用操作符(*)返回指針變量指向的內(nèi)容,通常又將“*”稱為解引用指針。當(dāng)指針變量為空指針時(shí),則“*指針變量”毫無意義。 如果ptr指向iNum,&iNum表示變量iNum的地址。使用*ptr等同于iNum,表示存儲(chǔ)在&iNum地址上的值。即除了可以通過*ptr輸出iNum的值,還可以賦值。比如: printf('%p\n', *ptr); 注意,%p與%x的區(qū)別在于,%p會(huì)將數(shù)字顯示為十六進(jìn)制大寫。 2. 直接訪問定義變量的目的是通過“變量名”引用“變量的值”,由于程序經(jīng)過編譯后已經(jīng)將“變量名”轉(zhuǎn)換為“變量的地址”,因此對(duì)變量的取值都是通過地址進(jìn)行的,則直接按變量名取值的訪問方式就是“直接訪問”。比如: iNum = 0x64; //對(duì)變量iNum的直接訪問 *ptr = 0x80; //對(duì)變量iNum的間接訪問 顯然,無論是采用間接訪問還是直接訪問,這2個(gè)語句作用是相同的。由于指針變量也是變量,因此在程序中同樣也可以直接使用,而不必通過間接訪問的方法。比如: int *ptr1,*ptr2; ptr1 =ptr2; 即這樣ptr1與ptr2指向同一個(gè)對(duì)象。另,也可以將ptr2指向的值復(fù)制到ptr1中。比如: *ptr1 =*ptr2; //數(shù)值賦值 綜上所述,指針存儲(chǔ)的是地址,因此直接使用“裸”指針得到的是地址。要獲取或調(diào)整存儲(chǔ)在該地址中的值,必須額外添加的“*”。而變量存儲(chǔ)的是數(shù)據(jù)值,因此直接使用變量得到的是數(shù)據(jù)值。要獲取變量的地址,必須額外添加“&”。 3. 強(qiáng)制類型轉(zhuǎn)換實(shí)際上指針(存儲(chǔ)單元的地址)也是無符號(hào)整數(shù),因此指針可以與整型變量的類型互相轉(zhuǎn)換。比如: unsigned int a = 5, b; b = (unsigned int)&a; 由于&a的類型為unsigned int*,因此需要強(qiáng)制轉(zhuǎn)換&a為unsigned int,只有這樣才能將變量a的地址保存在b中,那么將如何通過b取得a的值呢?必須先將b強(qiáng)制轉(zhuǎn)換為指針,才能讀取a的值,詳見程序清單 1.9。
在C語言中也常常遇到這樣的情況,如果要將數(shù)據(jù)0x05存入絕對(duì)地址0x22FF74中,那么下面這條語句是否正確呢? *0x22FF74= 0x05; 這是非法的。因?yàn)?x22FF74是int型整數(shù),而*間接訪問操作只能用于指針表達(dá)式。 既然通過指針可以向其指向的內(nèi)存地址寫入數(shù)據(jù),那么這里的內(nèi)存地址0x22FF74就是指針,因此必須先通過強(qiáng)制轉(zhuǎn)換將0x22FF74轉(zhuǎn)換為指向“unsigned int *”類型。然后通過“*”向0x22FF74內(nèi)存寫入數(shù)據(jù),“*(unsigned int *)0x22FF74”表示讀取0x22FF74地址里面的內(nèi)容,其內(nèi)容就是保存在地址為0x22FF74存儲(chǔ)器內(nèi)的數(shù)據(jù)。比如: *(unsigned int *)0x22FF74 = 0x05; printf('*(unsigned int *)0x22FF74 = 0x%x\n', *(unsigned int*)0x22FF74); 上述方法不是用于訪問某個(gè)變量,而是通過地址訪問內(nèi)存中某個(gè)特定的位置,比如,系統(tǒng)通過與輸入輸出設(shè)備控制器之間的通信,以及與I/O的輸入輸出操作來獲得相應(yīng)的結(jié)果。事實(shí)上,計(jì)算機(jī)與設(shè)備控制器的通信就是通過在某個(gè)特定內(nèi)存地址讀取和寫入值來實(shí)現(xiàn)的,表面上看起來這些操作訪問的是內(nèi)存,其實(shí)際上訪問的是設(shè)備控制器接口。 在強(qiáng)制類型轉(zhuǎn)換運(yùn)算符中和類型作為sizeof的操作數(shù)時(shí),雖然初學(xué)者對(duì)一些復(fù)雜的類型名感到難以理解,但實(shí)際上卻有規(guī)律可循。其聲明規(guī)則為在標(biāo)識(shí)符(變量名或函數(shù)名)的聲明中,將標(biāo)識(shí)符取出后,剩下的部分自然就是類型名。比如: void (*func)(); 其類型名為“void (*)()”。void (*)()是將void (*func)()的標(biāo)識(shí)符func去掉后形成的,所以該類型名被解釋為指向返回void函數(shù)的指針,func是指向返回void函數(shù)的指針。 同理,double*[3]是將double *p[3]的標(biāo)識(shí)符p去掉后形成的,所以該類型名被解釋為指向double的指針數(shù)組,p是指向double的數(shù)組(元素3個(gè))的指針。 在指針的定義中,void *表示通用指針的類型,它可以作為兩個(gè)具有特定類型指針之間相互轉(zhuǎn)換的橋梁。注意,“從C99版本開始,將void *類型指針賦值給其它類型指針時(shí),不再需要進(jìn)行強(qiáng)制類型轉(zhuǎn)換”。比如: int *pInt,; void *pVoid; pInt =pVoid; 當(dāng)函數(shù)可以接受任何類型的指針時(shí),則將其聲明為void *類型指針。比如: void*memcpy(void *dst, const void *s2, size_t n); 其作用是從s2復(fù)制n個(gè)字符到dst,并返回dst的值,任何類型的指針都可以傳入memcpy()函數(shù)中。如果void作為函數(shù)的返回類型,則表示不返回任何值。如果void位于參數(shù)列表中,則表示沒有參數(shù)。size_t是C標(biāo)準(zhǔn)庫中預(yù)定義的類型,專門用于保存變量的大小。 如果要開發(fā)可移植性高的程序,應(yīng)該避免對(duì)指針進(jìn)行強(qiáng)制類型轉(zhuǎn)換,同時(shí)不要用強(qiáng)制類型轉(zhuǎn)換掩蓋編譯器提示的警告。 綜上所述,指針存儲(chǔ)的是地址,直接使用指針得到是地址;要獲得或調(diào)整存儲(chǔ)在該地址中的值,必須添加額外的“*”。變量存儲(chǔ)的是數(shù)據(jù)值,因此直接使用變量得到的是數(shù)據(jù)值;而要獲得變量的地址,就必須額外添加“&”。注意,sizeof操作符可以用在void指針上,無法將這個(gè)操作符用在void上,比如: size_t size = sizeof(void *); size_t size = sizeof(void); size_t類型表示C中任何對(duì)象所能達(dá)到的最大長(zhǎng)度,它是無符號(hào)整數(shù)。size_t用作sizeof操作符的返回值類型,同時(shí)也是很多函數(shù)參數(shù)類型,比如,malloc和strlen。 注意,打印size_t類型的值時(shí),由于它是無符號(hào)整數(shù),因此不能選錯(cuò)格式,通常推薦的格式為%zu或%u或%lu。
閱讀 '' |
|