訂閱
糾錯(cuò)
加入自媒體

C語言中威力最大的指針底層原理和使用技巧講解

5. 操作指針變量

對(duì)指針變量的操作包括3個(gè)方面:

操作指針變量自身的值;獲取指針變量所指向的數(shù)據(jù);以什么樣數(shù)據(jù)類型來使用/解釋指針變量所指向的內(nèi)容。5.1 指針變量自身的值

int a = 20;這個(gè)語句是定義變量a,在隨后的代碼中,只要寫下a就表示要操作變量a中存儲(chǔ)的值,操作有兩種:讀和寫。

printf("a = %d ", a); 這個(gè)語句就是要讀取變量a中的值,當(dāng)然是20;
a = 100;這個(gè)語句就是要把一個(gè)數(shù)值100寫入到變量a中。

同樣的道理,int *pa;語句是用來定義指針變量pa,在隨后的代碼中,只要寫下pa就表示要操作變量pa中的值:

printf("pa = %d ", pa); 這個(gè)語句就是要讀取指針變量pa中的值,當(dāng)然是0x11223344;
pa = &a;這個(gè)語句就是要把新的值寫入到指針變量pa中。再次強(qiáng)調(diào)一下,指針變量中存儲(chǔ)的是地址,如果我們可以提前知道變量a的地址是 0x11223344,那么我們也可以這樣來賦值:pa = 0x11223344;

思考一下,如果執(zhí)行這個(gè)語句printf("&pa =0x%x ", &pa);,打印結(jié)果會(huì)是什么?

上面已經(jīng)說過,操作符&是用來取地址的,那么&pa就表示獲取指針變量pa的地址,上面的內(nèi)存模型中顯示指針變量pa是存儲(chǔ)在0x11223348這個(gè)地址中的,因此打印結(jié)果就是:&pa = 0x11223348。

5.2 獲取指針變量所指向的數(shù)據(jù)

指針變量所指向的數(shù)據(jù)類型是在定義的時(shí)候就明確的,也就是說指針pa指向的數(shù)據(jù)類型就是int型,因此在執(zhí)行printf("value = %d ", *pa);語句時(shí),首先知道pa是一個(gè)指針,其中存儲(chǔ)了一個(gè)地址(0x11223344),然后通過操作符*來獲取這個(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ù)類型來使用/解釋指針變量所指向的內(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型格式來讀取0x11223344地址處的數(shù)據(jù),因此將會(huì)打印value = 0(在計(jì)算機(jī)中,ASCII碼是用等價(jià)的數(shù)字來存儲(chǔ)的)。

這個(gè)例子中說明了一個(gè)重要的概念:在內(nèi)存中一切都是數(shù)字,如何來操作(解釋)一個(gè)內(nèi)存地址中的數(shù)據(jù),完全是由我們的代碼來告訴編譯器的。剛才這個(gè)例子中,雖然0x11223344這個(gè)地址開始的4個(gè)字節(jié)的空間中,存儲(chǔ)的是整型變量a的值,但是我們讓pc指針按照char型數(shù)據(jù)來使用/解釋這個(gè)地址處的內(nèi)容,這是完全合法的。

以上內(nèi)容,就是指針最根本的心法了。把這個(gè)心法整明白了,剩下的就是多見識(shí)、多練習(xí)的問題了。

三、指針的幾個(gè)相關(guān)概念

 1. const屬性

const標(biāo)識(shí)符用來表示一個(gè)對(duì)象的不可變的性質(zhì),例如定義:

const int b = 20;

在后面的代碼中就不能改變變量b的值了,b中的值永遠(yuǎn)是20。同樣的,如果用const來修飾一個(gè)指針變量:

int a = 20;
int b = 20;
int * const p = &a;

內(nèi)存模型如下:

這里的const用來修飾指針變量p,根據(jù)const的性質(zhì)可以得出結(jié)論:p在定義為變量a的地址之后,就固定了,不能再被改變了,也就是說指針變量pa中就只能存儲(chǔ)變量a的地址0x11223344。如果在后面的代碼中寫p = &b;,編譯時(shí)就會(huì)報(bào)錯(cuò),因?yàn)閜是不可改變的,不能再被設(shè)置為變量b的地址。

但是,指針變量p所指向的那個(gè)變量a的值是可以改變的,即:*p = 21;這個(gè)語句是合法的,因?yàn)橹羔榩的值沒有改變(仍然是變量c的地址0x11223344),改變的是變量c中存儲(chǔ)的值。

與下面的代碼區(qū)分一下:

int a = 20;
int b = 20;
const int *p = &a;
p = &b;

這里的const沒有放在p的旁邊,而是放在了類型int的旁邊,這就說明const符號(hào)不是用來修飾p的,而是用來修飾p所指向的那個(gè)變量的。所以,如果我們寫p = &b;把變量b的地址賦值給指針p,就是合法的,因?yàn)閜的值可以被改變。

但是這個(gè)語句*p = 21就是非法了,因?yàn)槎x語句中的const就限制了通過指針p獲取的數(shù)據(jù),不能被改變,只能被用來讀取。這個(gè)性質(zhì)常常被用在函數(shù)參數(shù)上,例如下面的代碼,用來計(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ù)來看待,然后才可以賦值給p2指針。

如果我們使用void *p2來定義p2指針,那么在賦值時(shí)就不需要進(jìn)行強(qiáng)制類型轉(zhuǎn)換了,例如:

int a = 20;
int *p1 = &a;
void *p2 = p1;

指針p2是void*型,意味著可以把任意類型的指針賦值給p2,但是不能反過來操作,也就是不能把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;

我們來看一個(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)心用戶傳來的指針具體指向什么數(shù)據(jù)類型,只是把數(shù)據(jù)挨個(gè)存儲(chǔ)到這個(gè)地址對(duì)應(yīng)的空間中。

第二個(gè)參數(shù)同樣如此,此外還添加了const修飾符,這樣就說明了memcpy函數(shù)只會(huì)從src指針處讀取數(shù)據(jù),而不會(huì)修改數(shù)據(jù)。

3. 空指針和野指針

一個(gè)指針必須指向一個(gè)有意義的地址之后,才可以對(duì)指針進(jìn)行操作。如果指針中存儲(chǔ)的地址值是一個(gè)隨機(jī)值,或者是一個(gè)已經(jīng)失效的值,此時(shí)操作指針就非常危險(xiǎn)了,一般把這樣的指針稱作野指針,C代碼中很多指針相關(guān)的bug就來源于此。

3.1 空指針:不指向任何東西的指針

在定義一個(gè)指針變量之后,如果沒有賦值,那么這個(gè)指針變量中存儲(chǔ)的就是一個(gè)隨機(jī)值,有可能指向內(nèi)存中的任何一個(gè)地址空間,此時(shí)萬萬不可以對(duì)這個(gè)指針進(jìn)行寫操作,因?yàn)樗锌赡苤赶騼?nèi)存中的代碼段區(qū)域、也可能指向內(nèi)存中操作系統(tǒng)所在的區(qū)域。

一般會(huì)將一個(gè)指針變量賦值為NULL來表示一個(gè)空指針,而C語言中,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ū),通過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,然后通過*p設(shè)置這個(gè)地址中的值為14,這是合法的。如果在釋放了p指針指向的空間之后,再使用*p來操作這段地址,那就是非常危險(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;  // 最好加上這一句。

聲明: 本文由入駐維科號(hào)的作者撰寫,觀點(diǎn)僅代表作者本人,不代表OFweek立場(chǎng)。如有侵權(quán)或其他問題,請(qǐng)聯(lián)系舉報(bào)。

發(fā)表評(píng)論

0條評(píng)論,0人參與

請(qǐng)輸入評(píng)論內(nèi)容...

請(qǐng)輸入評(píng)論/評(píng)論長(zhǎng)度6~500個(gè)字

您提交的評(píng)論過于頻繁,請(qǐng)輸入驗(yàn)證碼繼續(xù)

  • 看不清,點(diǎn)擊換一張  刷新

暫無評(píng)論

暫無評(píng)論

人工智能 獵頭職位 更多
掃碼關(guān)注公眾號(hào)
OFweek人工智能網(wǎng)
獲取更多精彩內(nèi)容
文章糾錯(cuò)
x
*文字標(biāo)題:
*糾錯(cuò)內(nèi)容:
聯(lián)系郵箱:
*驗(yàn) 證 碼:

粵公網(wǎng)安備 44030502002758號(hào)