ANSI C定義了sizeof關(guān)鍵字,用來獲取一個變量和數(shù)據(jù)類型在內(nèi)存中所占的存儲字節(jié)數(shù)。GNU 擴展了一個關(guān)鍵字,typeof用來獲取一個變量或表達式的類型。
int i;
typeof(i) j = 20;
typeof(int *) a;
int f();
typeof(f()) k;
在上面的代碼中,因為變量i的類型為int,所以typeof(i)就等于int,typeof(i) j = 20就相當(dāng)于int j = 20;typeof(int *) a;就相當(dāng)于int * a;函數(shù)也是有類型的,函數(shù)的類型就是其返回值類型,所以typeof(f()) k;就相當(dāng)于int k;。
typeof( typeof(int *)[5] ) a; //相當(dāng)于int * a[5];
typeof( int x[5]) y; //相當(dāng)于int y[5];
完美MAX(a,b)宏的誕生
在之前黑鳥的文章中,有提到過如何定義一個適用不同類型且無副作用的MAX宏。今天就帶大家復(fù)習(xí)一下:
青銅級別:
#define MAX(x,y) ( (x) > (y)?(x):(y) )
一般我們在教科書上看到的簡單的MAX宏定義如上。但他有兩個致命缺陷,一是兩個參數(shù)必須類型一致,二是參數(shù)不能帶副作用,即自增或自減。原因大家自己思考,當(dāng)然也可以查看我之前的文章。
白銀級別:
#define MAX(type,x,y) ({ \
type _x = x; \
type _y = y; \
_x > _y ? _x : _y; \
})
該方法通過添加一個type參數(shù)解決了不同類型變量比較的問題;通過定義臨時變量_x和_y成功解決了參數(shù)的副作用問題。但他還不是最好的。
黃金級別:
#define MAX(x,y) ({ \
typeof(x) _x = x; \
typeof(y) _y = y; \
_x > _y ? _x : _y; \
})
通過typeof直接獲取宏的參數(shù)類型,這樣我們就不必再單獨將參數(shù)的類型傳給宏了。到這里他已經(jīng)基本可以在江湖中有自己一席之地了,但離號令天下還有一步之遙。
磚石級別:
#define MAX(x,y) ({\
typeof(x) _x = x; \
typeof(y) _y = y; \
(void) ( &_x == &_y ); \
_x>_y ? _x : _y;\
})
該宏定義之所以稱之為鉆石級別,是因為他多了一句(void) ( &_x == &_y ); 該語句看起來貌似一句廢話,但其實用的很巧妙。它主要是用來檢測宏的兩個參數(shù)的數(shù)據(jù)類型是否相同。如果不相同,編譯器會給一個警告信息提醒開發(fā)人員。
waring:comparison of distinct pointer types lacks a cast
- 讓我們分析一下他是如何實現(xiàn)判斷他的兩個參數(shù)的數(shù)據(jù)類型是否一致。
從字面意思來看,他用來判斷兩個變量的地址是否相等。可能有人會說,兩個變量的地址怎么可能相等呢?但妙就妙在這個地方!當(dāng)該語句還未執(zhí)行到判斷兩個變量的地址是否相等的時候,編譯器首先要檢查兩個變量的數(shù)據(jù)類型是否相同。如果兩個變量的數(shù)據(jù)類型不相同的話,那么編譯器會有警告信息。當(dāng)然我們也就可以從中獲益。而如果兩個變量的數(shù)據(jù)類型相等的話,那么該語句在整個宏中也不起任何作用,同樣我們也沒任何損失,笑問這種無本生意為何不做呢?
所謂是驢子是馬拉出來溜溜!上面我們講了這么多,但sizeof關(guān)鍵字到底有什么作用呢?下面通過解剖內(nèi)核宏container_of來為大家展示sizeof關(guān)鍵字的強大作用和內(nèi)核設(shè)計者的巨大腦洞。
解剖內(nèi)核第一宏container_of
首先讓我們來膜拜一下天下第一宏的風(fēng)采:
#define offsetof( TYPE, MEMBER ) ( (size_t)&((TYPE *)0)->MEMBER )
#define container_of(ptr, type,member) ({ \
const typeof( ((type *)0)->member ) _mptr = (ptr); \
(type *)( (char *)_mptr - offsetof(type, member) );
})
它的主要作用就是:根據(jù)結(jié)構(gòu)體某一成員的地址,獲取這個結(jié)構(gòu)體的首地址。根據(jù)宏定義我們可以看到,這個宏有三個參數(shù),他們分別是:
- type:結(jié)構(gòu)體類型
- member :結(jié)構(gòu)體內(nèi)的成員
- ptr: 結(jié)構(gòu)體內(nèi)成員member的地址
也就是說,只要我們知道了一個結(jié)構(gòu)體的類型,結(jié)構(gòu)體內(nèi)某一成員的地址,就可以直接獲得這個結(jié)構(gòu)體的首地址。這個宏返回的就是這個結(jié)構(gòu)體的首地址。
container_of宏實現(xiàn)分析
作為一名Linux內(nèi)核驅(qū)動開發(fā)者,除了要面對各種手冊、底層寄存器,有時候還要應(yīng)付底層造輪子的事情。為了系統(tǒng)的穩(wěn)定和性能,有時候我們不得不深入底層,死磕某個模塊進行分析和優(yōu)化。底層的工作雖然很有挑戰(zhàn)性,但有時候也是很枯燥的。不像應(yīng)用開發(fā)那樣有意思,所以為了提高對工作的興趣,大家表面上雖然不說自己牛叉,但內(nèi)心深處一定要建立起自己的職位優(yōu)越感。人不可有傲氣,但不能無傲骨。我們可不像開發(fā),知道api接口、讀讀文檔、完成功能就ok了。作為一名底層開發(fā)者要時刻記住:要和寄存器、內(nèi)存、硬件電路等各種底層群眾打成一片。從群眾中來,到群眾中去,急群眾之所急,想群眾之所想。這樣才能構(gòu)建一個穩(wěn)定和諧的嵌入式社會。
- 首先來看offsetof宏
#define offsetof( TYPE, MEMBER ) ( (size_t)&((TYPE *)0)->MEMBER )
這個宏有兩個參數(shù),一個是結(jié)構(gòu)體類型TYPE,一個是結(jié)構(gòu)體的成員MEMBER。它使用的技巧就是:將0強制轉(zhuǎn)換為一個指向TYPE類型的結(jié)構(gòu)體常量指針。因為常量指針為0,即可以看做結(jié)構(gòu)體首地址為0,所以結(jié)構(gòu)體每個成員變量的地址即為該成員相對于結(jié)構(gòu)體首地址的偏移。最后通過強制類型轉(zhuǎn)換size_t,將成員地址值轉(zhuǎn)換為整數(shù),取得其在整個結(jié)構(gòu)體中的偏移。
- 再來看container_of宏
const typeof( ((type *)0)->member ) _mptr = (ptr);
因為結(jié)構(gòu)體的成員數(shù)據(jù)類型可以是任意的數(shù)據(jù)類型,所以為了讓這個宏兼容各種數(shù)據(jù)類型,我們定義了一個臨時指針變量_mptr,該變量用來存儲結(jié)構(gòu)體成員member的地址ptr。那如何獲取ptr指針類型的呢?就是通過上面的宏語句。
(type*)((char*)_mptr-offsetof(type,member));
最后通過該成員變量地址_mptr減去該成員在結(jié)構(gòu)體中的偏移,就得到了結(jié)構(gòu)體的首地址。當(dāng)然因為返回的是結(jié)構(gòu)體的首地址所以數(shù)據(jù)類型還必須強制轉(zhuǎn)換一下。
小思
好了到這里我們對天下第一宏的分析也就接近尾聲了。那么可以看出任何一個復(fù)雜的東西,我們都可以把它分解。運用所學(xué)的基礎(chǔ)知識一層一層,一點以一點去剖析,先去降維分析,然后再進行綜合。那么這種能力就是你的核心競爭力,也是你超越其他工程師脫穎而出的機會!