訂閱
糾錯
加入自媒體

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

5. 操作指針變量

對指針變量的操作包括3個方面:

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

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

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

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

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

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

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

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

指針變量所指向的數(shù)據(jù)類型是在定義的時候就明確的,也就是說指針pa指向的數(shù)據(jù)類型就是int型,因此在執(zhí)行printf("value = %d ", *pa);語句時,首先知道pa是一個指針,其中存儲了一個地址(0x11223344),然后通過操作符*來獲取這個地址(0x11223344)對應(yīng)的那個存儲空間中的值;又因為在定義pa時,已經(jīng)指定了它指向的值是一個int型,所以我們就知道了地址0x11223344中存儲的就是一個int類型的數(shù)據(jù)。

5.3 以什么樣的數(shù)據(jù)類型來使用/解釋指針變量所指向的內(nèi)容

如下代碼:

int a = 30000;
int *pa = &a;
printf("value = %d ", *pa);

根據(jù)以上的描述,我們知道printf的打印結(jié)果會是value = 30000,十進制的30000轉(zhuǎn)成十六進制是0x00007530,內(nèi)存模型如下:

現(xiàn)在我們做這樣一個測試:

char *pc = 0x11223344;
printf("value = %d ", *pc);

指針變量pc在定義的時候指明:它指向的數(shù)據(jù)類型是char型,pc變量中存儲的地址是0x11223344。當(dāng)使用*pc獲取指向的數(shù)據(jù)時,將會按照char型格式來讀取0x11223344地址處的數(shù)據(jù),因此將會打印value = 0(在計算機中,ASCII碼是用等價的數(shù)字來存儲的)。

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

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

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

 1. const屬性

const標識符用來表示一個對象的不可變的性質(zhì),例如定義:

const int b = 20;

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

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

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

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

但是,指針變量p所指向的那個變量a的值是可以改變的,即:*p = 21;這個語句是合法的,因為指針p的值沒有改變(仍然是變量c的地址0x11223344),改變的是變量c中存儲的值。

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

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

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

但是這個語句*p = 21就是非法了,因為定義語句中的const就限制了通過指針p獲取的數(shù)據(jù),不能被改變,只能被用來讀取。這個性質(zhì)常常被用在函數(shù)參數(shù)上,例如下面的代碼,用來計算一塊數(shù)據(jù)的CRC校驗,這個函數(shù)只需要讀取原始數(shù)據(jù),不需要(也不可以)改變原始數(shù)據(jù),因此就需要在形參指針上使用const修飾符:

short int getDataCRC(const char *pData, int len)

   short int crc = 0x0000;
   // 計算CRC
   return crc;

2. void型指針

關(guān)鍵字void并不是一個真正的數(shù)據(jù)類型,它體現(xiàn)的是一種抽象,指明不是任何一種類型,一般有2種使用場景:

函數(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ù)類型不同呢?必須進行強制類型轉(zhuǎn)換。例如:

int a = 20;
int *p1 = &a;
char *p2 = (char *)p1;

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

p1指針指向的是int型數(shù)據(jù),現(xiàn)在想把它的值(0x11223344)賦值給p2,但是由于在定義p2指針時規(guī)定它指向的數(shù)據(jù)類型是char型,因此需要把指針p1進行強制類型轉(zhuǎn)換,也就是把地址0x11223344處的數(shù)據(jù)按照char型數(shù)據(jù)來看待,然后才可以賦值給p2指針。

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

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

指針p2是void*型,意味著可以把任意類型的指針賦值給p2,但是不能反過來操作,也就是不能把void*型指針直接賦值給其他確定類型的指針,而必須要強制轉(zhuǎn)換成被賦值指針所指向的數(shù)據(jù)類型,如下代碼,必須把p2指針強制轉(zhuǎn)換成int*型之后,再賦值給p3指針:

int a = 20;
int *p1 = &a;
void *p2 = p1;
int *p3 = (int *)p2;

我們來看一個系統(tǒng)函數(shù):

void* memcpy(void* dest, const void* src, size_t len);

第一個參數(shù)類型是void*,這正體現(xiàn)了系統(tǒng)對內(nèi)存操作的真正意義:它并不關(guān)心用戶傳來的指針具體指向什么數(shù)據(jù)類型,只是把數(shù)據(jù)挨個存儲到這個地址對應(yīng)的空間中。

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

3. 空指針和野指針

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

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

在定義一個指針變量之后,如果沒有賦值,那么這個指針變量中存儲的就是一個隨機值,有可能指向內(nèi)存中的任何一個地址空間,此時萬萬不可以對這個指針進行寫操作,因為它有可能指向內(nèi)存中的代碼段區(qū)域、也可能指向內(nèi)存中操作系統(tǒng)所在的區(qū)域。

一般會將一個指針變量賦值為NULL來表示一個空指針,而C語言中,NULL實質(zhì)是 ((void*)0) , 在C++中,NULL實質(zhì)是0。在標準庫頭文件stdlib.h中,有如下定義:

#ifdef __cplusplus
    #define NULL    0
#else    
    #define NULL    ((void *)0)
#endif
3.2 野指針:地址已經(jīng)失效的指針

我們都知道,函數(shù)中的局部變量存儲在棧區(qū),通過malloc申請的內(nèi)存空間位于堆區(qū),如下代碼:

int *p = (int *)malloc(4);
*p = 20;

內(nèi)存模型為:

在堆區(qū)申請了4個字節(jié)的空間,然后強制類型轉(zhuǎn)換為int*型之后,賦值給指針變量p,然后通過*p設(shè)置這個地址中的值為14,這是合法的。如果在釋放了p指針指向的空間之后,再使用*p來操作這段地址,那就是非常危險了,因為這個地址空間可能已經(jīng)被操作系統(tǒng)分配給其他代碼使用,如果對這個地址里的數(shù)據(jù)強行操作,程序立刻崩潰的話,將會是我們最大的幸運!

int *p = (int *)malloc(4);
*p = 20;
free(p);
// 在free之后就不可以再操作p指針中的數(shù)據(jù)了。
p = NULL;  // 最好加上這一句。

<上一頁  1  2  3  下一頁>  
聲明: 本文由入駐維科號的作者撰寫,觀點僅代表作者本人,不代表OFweek立場。如有侵權(quán)或其他問題,請聯(lián)系舉報。

發(fā)表評論

0條評論,0人參與

請輸入評論內(nèi)容...

請輸入評論/評論長度6~500個字

您提交的評論過于頻繁,請輸入驗證碼繼續(xù)

暫無評論

暫無評論

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

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