從變數的三要素開始談起
為了把複雜的事情說簡單,我們拋開指標先從變數談起。(好吧,不知道這個笑話是不是夠冷)乙個變數(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)資訊」,我們可以簡化為:
變數的三要素中,型別資訊從不會額外占用物理儲存空間。
變數的三要素中,型別資訊從不會額外占用物理儲存空間。
變數的三要素中,型別資訊從不會額外占用物理儲存空間。
化繁為簡的威力
前面說了那麼多,實際上可以簡化為下面的等式:
variable = address value + type info
變數 = 位址數值 + 型別資訊
其中,位址數值的儲存、表達和運算是(有可能)實實在在需要占用物理儲存器空間的(ram和rom);而型別資訊則是編譯器專用的——僅僅在編譯時刻會用到,用來為編譯器語法檢測和生成**提供資訊的——話句話說,你只需要知道,型別資訊是乙個邏輯上的資訊,是虛的,在最終生成的程式中並不占用任何儲存器空間。你也可以理解為,型別資訊最終以程式行為的方式體現在**中,而並不占用任何額外的資料儲存器空間。
void 的字面意思是「空型別」, void *則為「空型別指標」, void *可以指向任何型別的資料。
const修飾的是唯讀的變數。const 定義的唯讀變數從彙編的角度來看, 只是給出了對應的記憶體位址, 是在編譯的時候確定其值。
const可以節省空間,避免不必要的記憶體分配,同時提高效率。
編譯器會把一切編譯時刻已經確定的東西盡可能只保留其語法意義的同時確保其不占用實際記憶體空間
根據這條規則,如果你做了乙個 結構體,結構體定義例項的時候用了乙個 const,那麼這個結構體很可能是可以避免實際占用空間的——除非你作死,對這個結構體做了取位址運算。如果這個const的結構體裡有函式指標,那麼對這個函式指標的訪問會變成普通函式呼叫——而不是借助函式指標呼叫——因為編譯器已經知道你的意圖,覺得沒必要真的經過乙個結構體。同理,如果裡面有其它普通整形變數,由於const的存在,編譯器可能會直接把他們當成立即數來處理。
這條規則本質是乙個編譯器優化規則,就是說,如果編譯時刻,資訊都是確定的,那麼我只要保留語法意義就行了(因為語法意義只有編譯器去遵守,並不需要占用存記憶體空間去儲存,屬於「虛」的東西),只有繞不開才分配。
舉個例子:你有個函式指標,你宣告為const,那麼通過這個函式指標訪問函式的地方就會變成直接對函式的呼叫,而這個函式指標實體是不會存在的——這就是用到了,但繞的開。如果你作死,對這個函式指標取位址,並且這個位址還被賦值給別的變數——那就是用到了,且繞不開,那真的就沒辦法,只能分配乙個實體了。
這個原則對編譯器來說是優化的指導原則,所以,不開優化,開不同等級的優化,不同編譯器,都有可能有行為上的差異。
const修飾指標:
const int *p; // p 可變, p 指向的物件不可變
int const *p; // p 可變, p 指向的物件不可變
int *const p; // p 不可變, p 指向的物件可變
const int *const p; //指標 p 和 p 指向的物件都不可變
先忽略型別名(編譯器解析的時候也是忽略型別名),我們看 const 離哪個近。 「近水樓
台先得月」,離誰近就修飾誰。
const int p; //const 修飾p,p 是指標, *p 是指標指向的物件,不可變
int const p; //const 修飾p,p 是指標, *p 是指標指向的物件,不可變
int *const p; //const 修飾 p, p 不可變, p 指向的物件可變
const int const p; //前乙個 const 修飾p,後乙個 const 修飾 p,指標 p 和 p 指向的物件都不可變
什麼是窺孔優化?
volatile 關鍵字可以關閉窺孔優化。
volatile修飾的變數表示可以被某些編譯器未知的因素更改,比如作業系統、硬體或者其它執行緒等。遇到這個關鍵字宣告的變數,編譯器對訪問該變數的**就不再進行優化,從而可以提供對特殊位址的穩定訪問。
volatile在多執行緒共享變數時,可以保證可見性。
例如以下例子:
int i=10;
int j = i; //(1)語句
int k = i; //(2)語句
這時候編譯器對**進行優化,因為在(1)、(2)兩條語句中, i 沒有被用作左值。這時候編譯器認為 i 的值沒有發生改變,所以在(1)語句時從記憶體中取出 i 的值賦給 j 之後,這個值並沒有被丟掉,而是在(2)語句時繼續用這個值給 k 賦值。編譯器不會生成出彙編**重新從記憶體裡取 i 的值,這樣提高了效率。但要注意: (1)、(2)語句之間 i 沒有被用作左值才行。
再看另乙個例子:
volatile int i=10;
int j = i; //(3)語句
int k = i; //(4)語句
volatile 關鍵字告訴編譯器 i 是隨時可能發生變化的,每次使用它的時候必須從記憶體中取出 i的值,因而編譯器生成的彙編**會重新從 i 的位址處讀取資料放在 k 中。
這樣看來,如果 i 是乙個暫存器變數或者表示乙個埠資料或者是多個執行緒的共享資料,就容易出錯,所以說 volatile 可以保證對特殊位址的穩定訪問,也就是乙個執行緒改變了共享變數的值,另乙個執行緒能立即可見。
如何對volatile 修飾的變數進行手工優化
static關鍵字的本質
用乙個例子來說明 一 1 新建乙個類stu,它有乙個屬性age public class stu2 再新建乙個測試類stutest public class stutest 二 1 修改stu類,把屬性age修改為static靜態屬性 public class stu2 此時再測試,執行結果為s1 ...
this關鍵字和super關鍵字
this關鍵字和super關鍵字都必須放在構造方法的第一行,所有兩個關鍵字不能同時存在,任選其一即可,只需保證子類的所有構造方法呼叫父類的構造方法即可 this關鍵字的應用 this關鍵字用於區分區域性變數和成員變數的同名問題 有this關鍵字的是成員變數,或者是誰呼叫我,訪問的就是誰 this關鍵...
python 變數 關鍵字
變數 在記憶體中開闢一塊空間,起乙個別名,用了訪問和儲存空間中的資料 變數的特點 可以反覆儲存資料 可以反覆取出資料 可以反覆更改資料 1 開頭是字母 或下劃線 後面可以是字母 數字 下劃線 abc true 12abc 錯誤 false name pass 特殊字元 2 不能以關鍵字名 關鍵字在p...