深度剖析C語言的各種指標

2021-07-31 22:00:15 字數 4596 閱讀 9596

該文章詳解了c語言中指標的概念與用法,對初學者了解c語言中指標這一重要概念有很大的幫助,其中變數的定義與使用一節尤為有價值。要點如下:

1. 指標是個變數,本質代表了乙個位址。

2. 變數拆解遵循由近到遠,從左到右的原則。

3. c語言中,變數怎麼使用就怎麼定義(指的是書寫格式上保持一致)。

在c語言中,指標就是乙個位址,不管是什麼型別的指標,它都代表了乙個位址,指標本身的值是乙個整形的數(長度跟系統和硬體有關,這裡認為指標長度是32位)。

指標的型別是對於指標指向的內容來說的,比如char型別的指標代表其指向的位址內容是char型別的,指定型別是為了在取資料的時候知道取多少個位元組。比如乙個指標名為ptr,假設指向0xffff0000,如下圖所示:

如果指標是char型別的,也就是char *ptr = 0xffff0000;如果我們取ptr所指向的值,就是0x01,依次類推,int型別的會取出4個位元組的資料,struct型別的則與struct的長度有關。

指標的型別本質上就是方便編譯器知道在取用指標指向的內容時應該取多少個位元組的資料

指標和陣列不同的地方是:指標是變數,它有自己的儲存空間(理解為普通變數即可,只不過指該變數的值是個指標),陣列名沒有儲存空間。所以指標本身是可以進行加減運算的,但是陣列名本身不可以進行加減運算。

/* 假設 "abc" 字串常量存放在從 0xffff0000 開始的位址空間 */

char str[4] = "abc";

char *ptr = "abc";

str++; //錯誤,不能夠進行自加減運算。str 應該理解為乙個位址常量,值為 0xffff0000

ptr++; //正確,ptr本身的值變為 0xffff0001。ptr 是個變數,有自己的儲存空間,可以使用

// printf("%d", (int)ptr); 來列印出ptr變數的存放位址

指標函式形如void *proc(void)

函式指標形如void (*proc)(void)

先看一段**:

#include 

void (*uart)(void);

unsigned

char test_str = "this is a test for a pointer of proc!";

void *uartx(void)

void uart0(void)

void set_handler(void (*handler)(void))

int main(int argc, char **argv)

對應於上面**片的

void (*uart)(void);

void *uartx(void)

void set_handler(void (*handler)(void))

set_handler(uart0);

uart();

這兩句中第一句傳遞函式指標,第二句是通過呼叫uart來呼叫uart0這個函式,uart本身就是指標,指向uart0函式的位址,這兩句列印出來「i am uart0」並且我們發現,形如

(*uart)();

(**uart)();

(*uart0)();

(**uart0)();

這樣的語句也都可以正確的編譯執行,由此可知函式名本身也許就可以看做乙個指向自己的位址(為方便理解),對函式名取位址還是函式名的位址值

如下**所示

void *uartx(void)

這段**雖指明了void型別,但是還有返回值,編譯器也未報錯,此處大概可以看作是void *型別的指標,也即無型別指標,倘若此處改為int或者long就會報錯,可見無型別指標與其餘型別的指標可以相容。

此時已經構造乙個指標函式,名為uartx,返回值為無型別指標,下面對函式進行呼叫

replace_str = (unsigned

char *)uartx();

printf("%s\n",replace_str);

改變replace_str的屬性也可以得到正確的結果,表明返回值確實是void 型別的,倘若將uartx改為返回unsigned char指標,replace_str改為long型指標就會報錯,但是若uartx是void 就不會報告任何資訊

void指標可用於不同型別指標參量傳遞,由上面的例子就可以看出,如果不確定函式返回的是什麼型別的值,就可以把返回值設為void指標型別,由此可以達到某些特殊的目的,但是大部分時候不建議這樣子編寫函式,因為這樣會造成函式返回值或者變數值的型別變得不確定,會為閱讀**帶來巨大困難,有些時候也會導致bug的產生,此時又不好找到原因,因此除了必須的特殊情況,void型別的指標盡量需要少用

下面列舉了一些比較複雜的指標變數定義,在非必要的情況下為了**便於閱讀還是不要這樣去定義變數的好

int (*daytab)[13];   //乙個指標,指標指向乙個擁有13個int型別值的陣列

int *daytab[13]; //乙個指標陣列,共13個指標,都指向int型別

char (*(*x()))(); //x是乙個指標函式,返回乙個指標,該指標乙個一位陣列,陣列元素為指標,每個指標都指向返回char型別的函式

char (*(*x[3])())[5]; //乙個指標陣列,共有3項,每項都指向乙個函式指標,並且該函式返回指向擁有5個char型別項的陣列的指標

上述指標應用實在是太過複雜,不是邏輯特別清晰的在編寫程式的時候千萬要避免這種寫法,否則會痛不欲生的

同普通變數一樣,在c語言中變數的定義遵循一條原則,就是定義與使用的書寫格式保持一致。比如:

char str[10]; // 定義

str[0] = 'a'; // 使用,可以看到兩者的結構都是乙個名字然後加乙個,這就是所謂的定義

// 與使用格式保持一致

char *ptr; // 定義

*ptr = 'a'; // 使用,都是 *ptr 格式

另外一條,變數定義的拆解遵循由近到遠,先右後左的原則進行,這樣一來我們去分析乙個比較複雜的定義就有據可循了,試著拆解下面乙個指標的定義:

int **ptr[10];

int **ptr[10][10];

int (*foo)(char);

int (*foo[10])(char);

先看一段**:

int start(void);

int pause(void);

int stop(void);

int (*foo[10])(void) = ;

int start(void)

int pause(void)

int stop(void)

int main(int argc, char *argv)

原則:變數的定義與使用在格式上是保持一致的

上面定義了乙個有10個指向引數為空返回值為int的函式的指標陣列,我們可以稱foo就是乙個函式指標陣列。main函式裡面的foo0;就代表呼叫了start這個函式。記憶體排布如下圖所示(位址均為隨便寫的,虛構,但是不妨礙理解):

foo[0]本身只是代表乙個位址,加上()之後就變成了乙個函式,我們可以把()理解為乙個操作符,該操作符的作用就是設定pc指標跳轉到某個位址執行**,有需要的時候設定函式的引數。當我們知道乙個函式的位址之後(假設是0xffff0010),我們可以直接用把該位址強制轉換為函式型別達到函式呼叫的作用(int (*0xffff0010)(void))();這個也是遵循怎麼使用就怎麼定義的原則。

其實c語言中很多東西都是非常容易理解的,但是由於包裹了系統這層厚厚的外殼,導致不那麼容易理解,這個時候需要透過現象看本質,最笨的方法是寫微控制器或者soc的裸機**,然後反彙編,對比一下反彙編之後的**與c語言**,很多疑惑或者誤解都會在瞬間煙消雲散。

有時候要理解c語言中的一些東西,首先就要搞清楚「它是什麼」,比如指標,指標實質上就是乙個變數,這個變數和普通變數沒什麼大的不同,如果緊緊的抓住指標是變數這一根源,那麼指標理解起來也就不是十分的困難了。

歡迎**、關注、點讚一波

C語言指標深度剖析

1.下標法,用a i 來訪問陣列元素 2.指標法,ptr i 形式間接訪問 3.陣列名,用 a i 形式訪問陣列 4.指標下標法,ptr i 形式訪問陣列元素 1.直接訪問法 printf d a i j 2.指標訪問法 int ptr ptr a 0 for i 0 i 3 i for j 0j ...

C語言深度剖析指標

1.指標是什麼?在電腦科學中,指標是程式語言中的乙個物件,利用位址,它的值直接指向存在電腦儲存器中另乙個地方的值。由於通過位址能找到所需的變數單元,可以說,位址指向該變數單元。因此,將位址形象化的稱為 指標 意思是通過它能找到以它為位址的記憶體單元。記憶體 最小記憶體單元 bit 1byte 8bi...

C語言深度剖析

c語言深度剖析 1,編譯器通常不為普通const唯讀變數分配儲存空間,而是將他們儲存在符號表中,使得它成為乙個編譯期間的值,沒有了儲存與讀記憶體的操作,使得它的效率更高。2,const int p p可變,p指向的物件不變。int const p p可變,p指向的物件不可變 int const p ...