C語(yǔ)言中威力最大的指針底層原理和使用技巧講解
5. 操作指針變量
對(duì)指針變量的操作包括3個(gè)方面:
操作指針變量自身的值;獲取指針變量所指向的數(shù)據(jù);以什么樣數(shù)據(jù)類型來(lái)使用/解釋指針變量所指向的內(nèi)容。5.1 指針變量自身的值
int a = 20;這個(gè)語(yǔ)句是定義變量a,在隨后的代碼中,只要寫下a就表示要操作變量a中存儲(chǔ)的值,操作有兩種:讀和寫。
printf("a = %d ", a); 這個(gè)語(yǔ)句就是要讀取變量a中的值,當(dāng)然是20;
a = 100;這個(gè)語(yǔ)句就是要把一個(gè)數(shù)值100寫入到變量a中。
同樣的道理,int *pa;語(yǔ)句是用來(lái)定義指針變量pa,在隨后的代碼中,只要寫下pa就表示要操作變量pa中的值:
printf("pa = %d ", pa); 這個(gè)語(yǔ)句就是要讀取指針變量pa中的值,當(dāng)然是0x11223344;
pa = &a;這個(gè)語(yǔ)句就是要把新的值寫入到指針變量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。如果在后面的代碼中寫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è)變量的。所以,如果我們寫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)行寫操作,因?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)更多
-
3月27日立即報(bào)名>> 【工程師系列】汽車電子技術(shù)在線大會(huì)
-
4月30日立即下載>> 【村田汽車】汽車E/E架構(gòu)革新中,新智能座艙挑戰(zhàn)的解決方案
-
5月15-17日立即預(yù)約>> 【線下巡回】2025年STM32峰會(huì)
-
即日-5.15立即報(bào)名>>> 【在線會(huì)議】安森美Hyperlux™ ID系列引領(lǐng)iToF技術(shù)革新
-
5月15日立即下載>> 【白皮書】精確和高效地表征3000V/20A功率器件應(yīng)用指南
-
5月16日立即參評(píng) >> 【評(píng)選啟動(dòng)】維科杯·OFweek 2025(第十屆)人工智能行業(yè)年度評(píng)選
推薦專題
- 1 UALink規(guī)范發(fā)布:挑戰(zhàn)英偉達(dá)AI統(tǒng)治的開(kāi)始
- 2 北電數(shù)智主辦酒仙橋論壇,探索AI產(chǎn)業(yè)發(fā)展新路徑
- 3 降薪、加班、裁員三重暴擊,“AI四小龍”已折戟兩家
- 4 “AI寒武紀(jì)”爆發(fā)至今,五類新物種登上歷史舞臺(tái)
- 5 國(guó)產(chǎn)智駕迎戰(zhàn)特斯拉FSD,AI含量差幾何?
- 6 光計(jì)算迎來(lái)商業(yè)化突破,但落地仍需時(shí)間
- 7 東陽(yáng)光:2024年扭虧、一季度凈利大增,液冷疊加具身智能打開(kāi)成長(zhǎng)空間
- 8 地平線自動(dòng)駕駛方案解讀
- 9 封殺AI“照騙”,“淘寶們”終于不忍了?
- 10 優(yōu)必選:營(yíng)收大增主靠小件,虧損繼續(xù)又逢關(guān)稅,能否乘機(jī)器人東風(fēng)翻身?