C語(yǔ)言中威力最大的指針底層原理和使用技巧講解
5. 操作指針變量
對(duì)指針變量的操作包括3個(gè)方面:
操作指針變量自身的值;獲取指針變量所指向的數(shù)據(jù);以什么樣數(shù)據(jù)類型來(lái)使用/解釋指針變量所指向的內(nèi)容。5.1 指針變量自身的值
int a = 20;這個(gè)語(yǔ)句是定義變量a,在隨后的代碼中,只要寫(xiě)下a就表示要操作變量a中存儲(chǔ)的值,操作有兩種:讀和寫(xiě)。
printf("a = %d ", a); 這個(gè)語(yǔ)句就是要讀取變量a中的值,當(dāng)然是20;
a = 100;這個(gè)語(yǔ)句就是要把一個(gè)數(shù)值100寫(xiě)入到變量a中。
同樣的道理,int *pa;語(yǔ)句是用來(lái)定義指針變量pa,在隨后的代碼中,只要寫(xiě)下pa就表示要操作變量pa中的值:
printf("pa = %d ", pa); 這個(gè)語(yǔ)句就是要讀取指針變量pa中的值,當(dāng)然是0x11223344;
pa = &a;這個(gè)語(yǔ)句就是要把新的值寫(xiě)入到指針變量pa中。再次強(qiáng)調(diào)一下,指針變量中存儲(chǔ)的是地址,如果我們可以提前知道變量a的地址是 0x11223344,那么我們也可以這樣來(lái)賦值:pa = 0x11223344;
思考一下,如果執(zhí)行這個(gè)語(yǔ)句printf("&pa =0x%x ", &pa);,打印結(jié)果會(huì)是什么?
上面已經(jīng)說(shuō)過(guò),操作符&是用來(lái)取地址的,那么&pa就表示獲取指針變量pa的地址,上面的內(nèi)存模型中顯示指針變量pa是存儲(chǔ)在0x11223348這個(gè)地址中的,因此打印結(jié)果就是:&pa = 0x11223348。
5.2 獲取指針變量所指向的數(shù)據(jù)
指針變量所指向的數(shù)據(jù)類型是在定義的時(shí)候就明確的,也就是說(shuō)指針pa指向的數(shù)據(jù)類型就是int型,因此在執(zhí)行printf("value = %d ", *pa);語(yǔ)句時(shí),首先知道pa是一個(gè)指針,其中存儲(chǔ)了一個(gè)地址(0x11223344),然后通過(guò)操作符*來(lái)獲取這個(gè)地址(0x11223344)對(duì)應(yīng)的那個(gè)存儲(chǔ)空間中的值;又因?yàn)樵诙xpa時(shí),已經(jīng)指定了它指向的值是一個(gè)int型,所以我們就知道了地址0x11223344中存儲(chǔ)的就是一個(gè)int類型的數(shù)據(jù)。
5.3 以什么樣的數(shù)據(jù)類型來(lái)使用/解釋指針變量所指向的內(nèi)容
如下代碼:
int a = 30000;
int *pa = &a;
printf("value = %d ", *pa);
根據(jù)以上的描述,我們知道printf的打印結(jié)果會(huì)是value = 30000,十進(jìn)制的30000轉(zhuǎn)成十六進(jìn)制是0x00007530,內(nèi)存模型如下:
現(xiàn)在我們做這樣一個(gè)測(cè)試:
char *pc = 0x11223344;
printf("value = %d ", *pc);
指針變量pc在定義的時(shí)候指明:它指向的數(shù)據(jù)類型是char型,pc變量中存儲(chǔ)的地址是0x11223344。當(dāng)使用*pc獲取指向的數(shù)據(jù)時(shí),將會(huì)按照char型格式來(lái)讀取0x11223344地址處的數(shù)據(jù),因此將會(huì)打印value = 0(在計(jì)算機(jī)中,ASCII碼是用等價(jià)的數(shù)字來(lái)存儲(chǔ)的)。
這個(gè)例子中說(shuō)明了一個(gè)重要的概念:在內(nèi)存中一切都是數(shù)字,如何來(lái)操作(解釋)一個(gè)內(nèi)存地址中的數(shù)據(jù),完全是由我們的代碼來(lái)告訴編譯器的。剛才這個(gè)例子中,雖然0x11223344這個(gè)地址開(kāi)始的4個(gè)字節(jié)的空間中,存儲(chǔ)的是整型變量a的值,但是我們讓pc指針按照char型數(shù)據(jù)來(lái)使用/解釋這個(gè)地址處的內(nèi)容,這是完全合法的。
以上內(nèi)容,就是指針最根本的心法了。把這個(gè)心法整明白了,剩下的就是多見(jiàn)識(shí)、多練習(xí)的問(wèn)題了。
三、指針的幾個(gè)相關(guān)概念
1. const屬性
const標(biāo)識(shí)符用來(lái)表示一個(gè)對(duì)象的不可變的性質(zhì),例如定義:
const int b = 20;
在后面的代碼中就不能改變變量b的值了,b中的值永遠(yuǎn)是20。同樣的,如果用const來(lái)修飾一個(gè)指針變量:
int a = 20;
int b = 20;
int * const p = &a;
內(nèi)存模型如下:
這里的const用來(lái)修飾指針變量p,根據(jù)const的性質(zhì)可以得出結(jié)論:p在定義為變量a的地址之后,就固定了,不能再被改變了,也就是說(shuō)指針變量pa中就只能存儲(chǔ)變量a的地址0x11223344。如果在后面的代碼中寫(xiě)p = &b;,編譯時(shí)就會(huì)報(bào)錯(cuò),因?yàn)閜是不可改變的,不能再被設(shè)置為變量b的地址。
但是,指針變量p所指向的那個(gè)變量a的值是可以改變的,即:*p = 21;這個(gè)語(yǔ)句是合法的,因?yàn)橹羔榩的值沒(méi)有改變(仍然是變量c的地址0x11223344),改變的是變量c中存儲(chǔ)的值。
與下面的代碼區(qū)分一下:
int a = 20;
int b = 20;
const int *p = &a;
p = &b;
這里的const沒(méi)有放在p的旁邊,而是放在了類型int的旁邊,這就說(shuō)明const符號(hào)不是用來(lái)修飾p的,而是用來(lái)修飾p所指向的那個(gè)變量的。所以,如果我們寫(xiě)p = &b;把變量b的地址賦值給指針p,就是合法的,因?yàn)閜的值可以被改變。
但是這個(gè)語(yǔ)句*p = 21就是非法了,因?yàn)槎x語(yǔ)句中的const就限制了通過(guò)指針p獲取的數(shù)據(jù),不能被改變,只能被用來(lái)讀取。這個(gè)性質(zhì)常常被用在函數(shù)參數(shù)上,例如下面的代碼,用來(lái)計(jì)算一塊數(shù)據(jù)的CRC校驗(yàn),這個(gè)函數(shù)只需要讀取原始數(shù)據(jù),不需要(也不可以)改變?cè)紨?shù)據(jù),因此就需要在形參指針上使用const修飾符:
short int getDataCRC(const char *pData, int len)
{
short int crc = 0x0000;
// 計(jì)算CRC
return crc;
}
2. void型指針
關(guān)鍵字void并不是一個(gè)真正的數(shù)據(jù)類型,它體現(xiàn)的是一種抽象,指明不是任何一種類型,一般有2種使用場(chǎng)景:
函數(shù)的返回值和形參;定義指針時(shí)不明確規(guī)定所指數(shù)據(jù)的類型,也就意味著可以指向任意類型。
指針變量也是一種變量,變量之間可以相互賦值,那么指針變量之間也可以相互賦值,例如:
int a = 20;
int b = a;
int *p1 = &a;
int *p2 = p1;
變量a賦值給變量b,指針p1賦值給指針p2,注意到它們的類型必須是相同的:a和b都是int型,p1和p2都是指向int型,所以可以相互賦值。那么如果數(shù)據(jù)類型不同呢?必須進(jìn)行強(qiáng)制類型轉(zhuǎn)換。例如:
int a = 20;
int *p1 = &a;
char *p2 = (char *)p1;
內(nèi)存模型如下:
p1指針指向的是int型數(shù)據(jù),現(xiàn)在想把它的值(0x11223344)賦值給p2,但是由于在定義p2指針時(shí)規(guī)定它指向的數(shù)據(jù)類型是char型,因此需要把指針p1進(jìn)行強(qiáng)制類型轉(zhuǎn)換,也就是把地址0x11223344處的數(shù)據(jù)按照char型數(shù)據(jù)來(lái)看待,然后才可以賦值給p2指針。
如果我們使用void *p2來(lái)定義p2指針,那么在賦值時(shí)就不需要進(jìn)行強(qiáng)制類型轉(zhuǎn)換了,例如:
int a = 20;
int *p1 = &a;
void *p2 = p1;
指針p2是void*型,意味著可以把任意類型的指針賦值給p2,但是不能反過(guò)來(lái)操作,也就是不能把void*型指針直接賦值給其他確定類型的指針,而必須要強(qiáng)制轉(zhuǎn)換成被賦值指針?biāo)赶虻臄?shù)據(jù)類型,如下代碼,必須把p2指針強(qiáng)制轉(zhuǎn)換成int*型之后,再賦值給p3指針:
int a = 20;
int *p1 = &a;
void *p2 = p1;
int *p3 = (int *)p2;
我們來(lái)看一個(gè)系統(tǒng)函數(shù):
void* memcpy(void* dest, const void* src, size_t len);
第一個(gè)參數(shù)類型是void*,這正體現(xiàn)了系統(tǒng)對(duì)內(nèi)存操作的真正意義:它并不關(guān)心用戶傳來(lái)的指針具體指向什么數(shù)據(jù)類型,只是把數(shù)據(jù)挨個(gè)存儲(chǔ)到這個(gè)地址對(duì)應(yīng)的空間中。
第二個(gè)參數(shù)同樣如此,此外還添加了const修飾符,這樣就說(shuō)明了memcpy函數(shù)只會(huì)從src指針處讀取數(shù)據(jù),而不會(huì)修改數(shù)據(jù)。
3. 空指針和野指針
一個(gè)指針必須指向一個(gè)有意義的地址之后,才可以對(duì)指針進(jìn)行操作。如果指針中存儲(chǔ)的地址值是一個(gè)隨機(jī)值,或者是一個(gè)已經(jīng)失效的值,此時(shí)操作指針就非常危險(xiǎn)了,一般把這樣的指針?lè)Q作野指針,C代碼中很多指針相關(guān)的bug就來(lái)源于此。
3.1 空指針:不指向任何東西的指針
在定義一個(gè)指針變量之后,如果沒(méi)有賦值,那么這個(gè)指針變量中存儲(chǔ)的就是一個(gè)隨機(jī)值,有可能指向內(nèi)存中的任何一個(gè)地址空間,此時(shí)萬(wàn)萬(wàn)不可以對(duì)這個(gè)指針進(jìn)行寫(xiě)操作,因?yàn)樗锌赡苤赶騼?nèi)存中的代碼段區(qū)域、也可能指向內(nèi)存中操作系統(tǒng)所在的區(qū)域。
一般會(huì)將一個(gè)指針變量賦值為NULL來(lái)表示一個(gè)空指針,而C語(yǔ)言中,NULL實(shí)質(zhì)是 ((void*)0) , 在C++中,NULL實(shí)質(zhì)是0。在標(biāo)準(zhǔn)庫(kù)頭文件stdlib.h中,有如下定義:
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
3.2 野指針:地址已經(jīng)失效的指針
我們都知道,函數(shù)中的局部變量存儲(chǔ)在棧區(qū),通過(guò)malloc申請(qǐng)的內(nèi)存空間位于堆區(qū),如下代碼:
int *p = (int *)malloc(4);
*p = 20;
內(nèi)存模型為:
在堆區(qū)申請(qǐng)了4個(gè)字節(jié)的空間,然后強(qiáng)制類型轉(zhuǎn)換為int*型之后,賦值給指針變量p,然后通過(guò)*p設(shè)置這個(gè)地址中的值為14,這是合法的。如果在釋放了p指針指向的空間之后,再使用*p來(lái)操作這段地址,那就是非常危險(xiǎn)了,因?yàn)檫@個(gè)地址空間可能已經(jīng)被操作系統(tǒng)分配給其他代碼使用,如果對(duì)這個(gè)地址里的數(shù)據(jù)強(qiáng)行操作,程序立刻崩潰的話,將會(huì)是我們最大的幸運(yùn)!
int *p = (int *)malloc(4);
*p = 20;
free(p);
// 在free之后就不可以再操作p指針中的數(shù)據(jù)了。
p = NULL; // 最好加上這一句。
發(fā)表評(píng)論
請(qǐng)輸入評(píng)論內(nèi)容...
請(qǐng)輸入評(píng)論/評(píng)論長(zhǎng)度6~500個(gè)字
最新活動(dòng)更多
-
10月31日立即下載>> 【限時(shí)免費(fèi)下載】TE暖通空調(diào)系統(tǒng)高效可靠的組件解決方案
-
即日-11.13立即報(bào)名>>> 【在線會(huì)議】多物理場(chǎng)仿真助跑新能源汽車
-
11月28日立即報(bào)名>>> 2024工程師系列—工業(yè)電子技術(shù)在線會(huì)議
-
12月19日立即報(bào)名>> 【線下會(huì)議】OFweek 2024(第九屆)物聯(lián)網(wǎng)產(chǎn)業(yè)大會(huì)
-
即日-12.26火熱報(bào)名中>> OFweek2024中國(guó)智造CIO在線峰會(huì)
-
即日-2025.8.1立即下載>> 《2024智能制造產(chǎn)業(yè)高端化、智能化、綠色化發(fā)展藍(lán)皮書(shū)》
推薦專題
- 1 【一周車話】沒(méi)有方向盤(pán)和踏板的車,你敢坐嗎?
- 2 特斯拉發(fā)布無(wú)人駕駛車,還未迎來(lái)“Chatgpt時(shí)刻”
- 3 特斯拉股價(jià)大跌15%:Robotaxi離落地還差一個(gè)蘿卜快跑
- 4 馬斯克給的“驚喜”夠嗎?
- 5 海信給AI電視打樣,12大AI智能體全面升級(jí)大屏體驗(yàn)
- 6 打完“價(jià)格戰(zhàn)”,大模型還要比什么?
- 7 馬斯克致敬“國(guó)產(chǎn)蘿卜”?
- 8 神經(jīng)網(wǎng)絡(luò),誰(shuí)是盈利最強(qiáng)企業(yè)?
- 9 比蘋(píng)果偉大100倍!真正改寫(xiě)人類歷史的智能產(chǎn)品降臨
- 10 諾獎(jiǎng)進(jìn)入“AI時(shí)代”,人類何去何從?
- 高級(jí)軟件工程師 廣東省/深圳市
- 自動(dòng)化高級(jí)工程師 廣東省/深圳市
- 光器件研發(fā)工程師 福建省/福州市
- 銷售總監(jiān)(光器件) 北京市/海淀區(qū)
- 激光器高級(jí)銷售經(jīng)理 上海市/虹口區(qū)
- 光器件物理工程師 北京市/海淀區(qū)
- 激光研發(fā)工程師 北京市/昌平區(qū)
- 技術(shù)專家 廣東省/江門(mén)市
- 封裝工程師 北京市/海淀區(qū)
- 結(jié)構(gòu)工程師 廣東省/深圳市