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