一文讀懂指標的本質

2021-10-09 07:22:11 字數 4082 閱讀 8241

【在前面的話】

不得不說,看了太多的人在各種地方討論指標……越發看下去,越發覺得簡單的事情被搞那麼複雜,真是夠了,求求你們,放開那個變數,讓我來!

【正文】

1、從變數的三要素開始談起

為了把複雜的事情說簡單,我們拋開指標先從變數談起。(好吧,不知道這個笑話是不是夠冷)乙個變數(variable),或者順便相容下物件導向(oo)的概念,我們統一稱為物件(object),除了儲存於其中的內容以外,只有三個要素:

其中,我們習慣於把後兩者合併在一起稱之為,變數的"型別"。

位址數值(address value)

位址的數值是乙個無符號整數,其位寬由cpu的位址匯流排寬度所決定。話雖如此,其實主要還是編譯器在權衡了「使用者編寫**的便利性」以及「生成機器碼的效率」後為我們提供的解決方案:例如,針對8位機,編譯器普遍以等效為uint16_t的整數來儲存位址資訊;針對16位機和32位機,編譯器則普遍選擇uint32_t的整數來儲存位址資訊;針對64位機,編譯器則可能會提供兩種指標型別,分別用來對應uint32_t的4g位址空間和由uint64_t所代表的恐怖位址空間……

提問,8086有20根位址線,請問用哪種整形來表示其位址呢?(uint16_t、uint32_t還是uint20_t)——由於uint20_t並不存在,也並不適合cpu進行位址運算,所以統一用uint32_t來表示最為方便。

總而言之,位址的數值是乙個無符號整數。知道這個有什麼用呢?我們待一會再說。這裡我們需要強調一句廢話:位址的數值既然是整數,那麼它就可以用另外的變數(型別合適的整形變數或者指標變數)進行儲存——任何指標變數,其本質,首先是乙個無符號整形變數。任何指標常量,其本質首先是乙個無符號整數。

請一定要記住(重要的事情說三遍):

變數的三要素中,僅有位址值有可能會占用物理儲存空間。

變數的三要素中,僅有位址值有可能會占用物理儲存空間。

變數的三要素中,僅有位址值有可能會占用物理儲存空間。

大小(size)和對齊

如果僅從變數的大小來看整個計算機世界,就好像一副彩色被二值化了,到處是memory block,他們的尺寸通常是1個位元組、2個位元組、4個位元組、8個位元組、16個位元組或者由他們組合而成的長度各異block。這些block通常被編譯器在**生成的時候對其到位址的寬度上,比如位址寬度是32bit的,就對齊到4位元組,位址寬度是16bit的,就對齊到2位元組……

如果你習慣於使用組合語言來進行開發,你一定能體會我所描述的這種感覺。這些你統統都可以忘記,但有一點絕對要記住(重要的事情說三遍):

變數的三要素中,大小值從不會額外占用物理儲存空間。

變數的三要素中,大小值從不會額外占用物理儲存空間。

變數的三要素中,大小值從不會額外占用物理儲存空間。

c語言中,可以用sizeof( )來獲取乙個變數的大小。前面我們說過,指標首先是乙個整形變數,那麼容易知道:

uint8_t *pchobj;

uint16_t *phwobj;

uint32_t *pwobj;

sizeof(pchobj) 、sizeof(phwobj)、sizeof(pwobj)以及sizeof任意其它指標的結果都是一樣的,都是當前系統儲存位址數值的整形變數的寬度。對32位機來說,這個數值就是4——因為,sizeof( ) 求的是括號內變數的寬度,而指標變數首先是乙個整形變數!同一cpu中同一定址能力的指標,其寬度是一樣一樣一樣的!

乙個型別的大小資訊除了描述乙個變數所占用的儲存器尺寸以外,還隱含了該變數的對齊資訊。從結論來說,32位處理器架構下:

struct example_t 

;

適用的方法(method)和運算(operation)

對物件導向中的物件來說,方法就是該物件類中描述的各種成員函式(method);

對資料結構中的各類抽象資料型別(adt,abstract data type)來說,就是各類針對該資料型別的操作函式,比如鍊錶的新增(add)、插入(insert)、刪除(delete)、和查詢(search)操作;比如佇列物件的入隊(enqueue)、出隊(dequeue)函式;比如棧物件的入棧(push)、出棧(pop)等等……

對普通數值類的變數來說,就是所適用的各類運算,比如針對 int的四則運算(+、-、*、/、>、<、==、!=…)。你不能對float型的資料進行移位操作,為什麼呢?因為不同的型別擁有不同的適用方法和運算。

也許你已經猜到了,型別所適用的方法和運算也不會占用物理儲存空間。由於變數的「大小資訊」和「適用的方法和運算資訊」統稱為「型別(type)資訊」,我們可以簡化為:

變數的三要素中,型別資訊從不會額外占用物理儲存空間。

變數的三要素中,型別資訊從不會額外占用物理儲存空間。

變數的三要素中,型別資訊從不會額外占用物理儲存空間。

2、化繁為簡的威力

前面說了那麼多,實際上可以簡化為下面的等式:

variable = address value + type info

變數 = 位址數值 + 型別資訊

其中,位址數值的儲存、表達和運算是(有可能)實實在在需要占用物理儲存器空間的(ram和rom);而型別資訊則是編譯器專用的——僅僅在編譯時刻會用到,用來為編譯器語法檢測和生成**提供資訊的——話句話說,你只需要知道,型別資訊是乙個邏輯上的資訊,是虛的,在最終生成的程式中並不占用任何儲存器空間。你也可以理解為,型別資訊最終以程式行為的方式體現在**中,而並不占用任何額外的資料儲存器空間。

既然知道了變數的本質,我們就可以隨心所欲了,比如,我們可以隨意建立乙個全域性變數:

#define s_wmyvariable    (* (( uint32_t *) 0x12345678))
定義乙個巨集來替我們批量生產全域性變數:

#define __var(__type, __addr)     (*(__type *) (__addr))
使用起來也很方便,例如:

__var

(float

,0x20004000)=

3.1415926;

萬能型別轉換:

#define convert(  __addr,   __type )            \

__var( (__type), (uint32_t) (__addr) )

萬能型別轉換應用舉例:

例如,我們可以直接將位元組陣列中某一段內容擷取出來,當做某種型別的變數來訪問:

//! 某資料幀解析函式

void

command_handler

( uint8_t *pchstream, uint16_t hwlength )

請忘記指標:

如果你是乙個指標苦手,那麼請忘記之前所學的一切。記住一句話:指標只是乙個用法怪異的整形變數,專門用來儲存變數的位址數值。指標的型別都是用來欺騙編譯器的,我是聰明的人類,我操縱型別,我不是愚蠢的編譯器。

推論:因為指標變數的本質是整形變數,所以指向指標的指標,只不過是乙個指向普通整形變數的普通指標,因此指向指標的指標並不存在——世界上只存在普通指標——世界上只存在用法怪異的整形變數,專門用來儲存目標變數的位址數值。

推論:世界上並不存在指向指標的指標的指標的指標……

給我乙個整數,我自己造自己的變數。指標的數值運算太坑?轉換成整數,加減乘除,隨便整。

小結:指標:指標分為指標常量和指標變數,單獨說指標的時候,通常指指標常量。其中:

指標常量 = 位址數值(常數)+ 型別資訊

指標變數 = 整形變數 + 型別資訊

變數 = (* 指標)

指標 = &變數

指標常量 = 整數常量 + 型別資訊

也就是:

指標常量 = (《型別資訊》 *)整數 常量

反過來也成立:

整數常數 = 指標常量 - 型別資訊

也就是:

整數常數 = (unsigned int)指標常量

一文讀懂Nginx

問 nginx的負載均衡演算法有什麼?預設是什麼演算法?答 1 輪詢 按請求的時間輪詢查空閒的後端伺服器 2 指定輪詢機率 機率的原因是後端伺服器的效能不均勻,好的多分點,差的少分點 3 固定ip繫結固定伺服器 預設是加權輪詢,就是優先訪問權重高的伺服器 問 nginx是單執行緒的嗎?答 是單執行緒...

一文讀懂SpringMVC

主要講的是dispatcherservlet這個類 ioc其實是乙個map,工程啟動後掃瞄路徑,根據類的全限定名建立bean 問 怎麼根據路徑找到方法?map還存key為 aaa value為該controller例項 問 autowired原理?自定義註解,在載入的時候,掃瞄controller層...

堆疊 一文讀懂

堆疊 stack 是一種先進後出的 操作受限的線性表,也可以直接稱為棧。可以把棧想象成乙個桶一樣,往這個桶裡面一層一層的放東西,先放進去的在裡面,後放進去的東西依次在外面。但取東西的時候就是先取靠近外面的,再依次一層層取裡面的。這就是 後進先出 last in first out 的原則。因此 棧 ...