Linux操作系統(tǒng),可以說它就是程序猿的代碼天堂;這不僅僅因為它是開源的,更多的是因為它的誕生,是由世界上無數(shù)的代碼天才共同締造而來;跑在它上面的Linux內(nèi)核,經(jīng)受了世界上各式各樣的服務(wù)器壓力測試,始終保持著高效、穩(wěn)定、安全的特性,一如既往地服務(wù)全人類。甚至可以說Linux操作系統(tǒng)造福了人類,很難想象,當(dāng)Linux操作系統(tǒng)消失了,這個世界會變得怎么樣?
? 作為Linux操作系統(tǒng)的忠實粉絲,筆者自大學(xué)時期就開始研究和使用Linux操作系統(tǒng),出來工作了好幾年,幾乎每天都要跟Linux系統(tǒng)打交道,甚至毫不夸張的是,白天不在Linux系統(tǒng)命令行下敲幾行命令,晚上都會失眠。
? 學(xué)習(xí)和使用了Linux系統(tǒng)這么些年,一直想找個機會,對Linux的知識做一番梳理,無奈之前礙于各種時間因素和自我的惰性,遲遲未有實質(zhì)性的進展。最近才開始狠狠地下定決心,必須邁出扎實的一步,爭取做出更多的分享,充實自我的同時,也給同行帶來更多的視野和思路,何樂而不為呢?
? 本文打算從一個很小的代碼設(shè)計,試圖從中窺探一下Linux內(nèi)核代碼的精妙設(shè)計。它的名字就叫 max宏定義,請跟隨筆者的思路一步步解開它神秘的面紗。
? 先來一個它的全貌:
#define max(a, b) ({\
typeof(a) _max1 = (a);\
typeof(b) _max2 = (b);\
(void)(&_max1 == &_max2);\
_max1 > _max2 ? _max1 : _max2; })
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-AQSFb6aB-1661923667090)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
? 我們先不一下子就把這段代碼剖析徹底,換個思維,假設(shè)我們是Linux內(nèi)核的設(shè)計者,要解決比較2個數(shù)的大小,代碼應(yīng)該怎么樣入手。我想很多C語言工作者,甚至是初學(xué)C語言的碼農(nóng)也可以寫出這樣的如下代碼:
#define max(a, b) a > b ? a : b
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-jvjzDDsR-1661923667093)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
? 初看這個宏定義,似乎沒有問題;細細一看,用個測試案例一測試就發(fā)現(xiàn)端倪了:
/* 假設(shè)有如下的調(diào)用代碼 */
{
printf("result = %d\n", max(9!=9, 0==0));
/* 宏定義展開后是 9!=9 > 0==0 ? 9!=9 : 0==0*/
/* 輸出結(jié)果是 0*/
}
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-59Bp6ggT-1661923667094)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
? 很明顯正確答案應(yīng)該是輸出1,細心者就很快發(fā)現(xiàn),給a和b加上括號試試看:
#define max(a, b) (a) > (b) ? (a) : (b)
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-k7e0TQdq-1661923667097)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
? 調(diào)用代碼測試下:
/* 假設(shè)有如下的調(diào)用代碼 */
{
printf("result = %d\n", max(9!=9, 0==0));
/* 宏定義展開后是 (9!=9) > (0==0) ?(9!=9):(0==0)*/
/* 輸出正確結(jié)果 1*/
printf("result = %d\n", 9 + max(9!=9, 0==0));
/* 宏定義展開后是 9 + (9!=9) > (0==0) ?(9!=9 :(0==0)*/
/* 輸出結(jié)果是0, 正確的期望值輸出,應(yīng)該是10 (=9+1) */
}
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-m705RxoN-1661923667114)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
? 于是又有了下面的改進:
#define max(a, b) ((a) > (b) ? (a) : (b))
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-xriR0pv8-1661923667116)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
? 這個版本,也是我們?nèi)粘懘a最經(jīng)常看到的版本,我們使用測試代碼測試下看看:
/* 假設(shè)有如下的調(diào)用代碼 */
{
printf("result = %d\n", 9 + max(9!=9, 0==0));
/* 宏定義展開后是 9 + ((9!=9) > (0==0) ?(9!=9):(0==0))*/
/* 輸出正確的期望值10 (=9+1) */
int a = 8;
int b = 9;
printf("result = %d\n", max(a++, b++));
/* 宏定義展開后是 ((a++) > (b++) ?(a++):(b++))*/
/* 輸出結(jié)果是10;而正確的期望值輸出,應(yīng)該是9 */
}
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-2JAec1EG-1661923667122)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
? 很遺憾,經(jīng)測試,這個版本依然有問題,這是因為宏定義中的++操作干擾了比較結(jié)果的輸出,我們需要再次改進這個宏定義。應(yīng)該怎么樣改進呢?既然是++操作干擾了輸出,那么我們使用2個中間變量來中轉(zhuǎn)下不就ok了嗎?于是有了下面的版本:
#define max(a, b) ({\
int _a = (a);\
int _b = (b);\
_a > _b ? _a : _b;\
})
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-t7VD8Szr-1661923667123)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
? 這樣的寫法,已經(jīng)有點接近Linux內(nèi)核定義的模樣了。再次使用上面的測試代碼執(zhí)行測試:
/* 假設(shè)有如下的調(diào)用代碼 */
{
int a = 8;
int b = 9;
printf("result = %d\n", max(a++, b++));
/* 宏定義展開后是 ({int _a=a++; int _b=b++; _a > _b ? _a : _b;})*/
/* 輸出正確的期望值9 */
}
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-dpinR0OT-1661923667125)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
? 雖然上面版本的定義解決了++操作符引起的輸出結(jié)果錯誤的問題,但是由于宏定義內(nèi)部使用了int型的_a和_b作為中間變量,這就是限制了max宏定義只能用于2個int型的數(shù)據(jù)做比較,這將大大限制了它的使用范圍。于是,很容易想到一個解決辦法,將int這個數(shù)據(jù)類型使用type變量傳進去,于是有了下面的版本:
#define max(type, a, b) ({\
type _a = (a);\
type _b = (b);\
_a > _b ? _a : _b;\
})
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-WVFbRavF-1661923667126)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
? 這樣的確是解決了如上數(shù)據(jù)類型問題的困惑,但是這樣我們的宏定義是以多一個參數(shù)輸入為犧牲代價的。那么,我們有沒有什么辦法,可以不將type輸入,而直接從輸入的a和b中獲取它們的數(shù)據(jù)類型呢?答案是肯定有的!
? GNU C作為C語言的擴展版本,增加了若干非常有用的擴展語法,其中typeof關(guān)鍵字就是其中的一個。比如定義一個變量int a; 則typeof(a)就可以取得a變量的類型,即int;比如直接使用typeof(unsigned char *),得到的輸出就是數(shù)據(jù)類型unsigned char *,非常的實用。于是我們將typeof應(yīng)用到max宏中,于是就有下面的優(yōu)良版本:
#define max(a, b) ({\
typeof(a) _a = (a);\
typeof(a) _b = (b);\
_a > _b ? _a : _b;\
})
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-KTnbdo0C-1661923667127)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
? 這樣的寫法,雖然避免了我們傳遞a和b變量的數(shù)據(jù)類型進去,但是,如下的測試代碼,結(jié)果會怎么樣呢?
/* 假設(shè)有如下的調(diào)用代碼 */
{
int a = 8;
float b = 9.0;
printf("result = %d\n", max(a, b));
/* 這樣能比較嗎?*/
int a = 8;
float b = 9.0;
float *p = &b;
printf("result = %d\n", max(a, p));
/* 這樣又能比較嗎?*/
}
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-j0v9A1VG-1661923667128)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
? 很明顯,當(dāng)a是int型,而b是float型,內(nèi)核執(zhí)行比較是可以的;但是如果拿一個int型的變量跟一個float *變量做比較,或者兩個奇奇怪怪的struct類型變量做計較,這樣肯定是不行的。所以,我們在設(shè)計max宏定義的時候,需要將這種可能出現(xiàn)的問題盡可能地在編譯階段就暴露出來,于是有了Linux內(nèi)核max宏定義的最佳版本:
#define max(a, b) ({\
typeof(a) _a = (a);\
typeof(a) _b = (b);\
(void) &_a == &_b;\
_a > _b ? _a : _b;\
})
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-YVJm8AAy-1661923667129)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
? 我們注意,宏定義的第4行,(void) &_a == &_b; 意思是對_a和_b的地址做比較,實際上從運行結(jié)果上,這個肯定是不等的,但是我們關(guān)心的并不是兩者比較的結(jié)果,而是兩者能不能用==比較的問題。當(dāng)_a和_b的數(shù)據(jù)類型一致時,代碼編譯不會有任何警告;反之,當(dāng)兩者的數(shù)據(jù)類型不一致時,比如之前的a是int型,而b是float型,那么這條語句就會報出編譯警告,如果在嚴格的編譯選項下,這個警告還可以轉(zhuǎn)換為錯誤,要求代碼調(diào)用者去確認結(jié)果,是否對兩個不同類型的數(shù)據(jù)執(zhí)行max比較的動作,從而將隱患消除,提升代碼質(zhì)量。
? 通過跟隨筆者的思路,我們可以細細地體會到,內(nèi)核設(shè)計者在設(shè)計這個max宏時,相信也是走了不少的彎路,從一開始最簡版本,接著遇到各式各樣的問題,然后一步步解決,一步步完善設(shè)計,最終才有最優(yōu)秀的max宏呈現(xiàn)在我們面前。如此之類的代碼設(shè)計,在Linux內(nèi)核設(shè)計代碼中比比皆是,今后筆者也會集中整理此類的優(yōu)秀設(shè)計,致力于將更多的優(yōu)秀內(nèi)核代碼分享給讀者,敬請關(guān)注。文中提及的觀點,均為筆者愚見,如有紕漏之處,還望誠心指正,謝謝。
-
內(nèi)核
+關(guān)注
關(guān)注
3文章
1408瀏覽量
41084 -
Linux
+關(guān)注
關(guān)注
87文章
11456瀏覽量
212750 -
操作系統(tǒng)
+關(guān)注
關(guān)注
37文章
7077瀏覽量
124932
發(fā)布評論請先 登錄
Linux內(nèi)核中C語言宏的使用技巧

Linux的內(nèi)核教程
Linux內(nèi)核配置系統(tǒng)詳解
linux內(nèi)核是什么_linux內(nèi)核學(xué)習(xí)路線
linux內(nèi)核參數(shù)設(shè)置_linux內(nèi)核的功能有哪些

最硬核的Linux內(nèi)核文章

快速理解什么是Linux內(nèi)核以及Linux內(nèi)核的內(nèi)容

Linux 5.10.5內(nèi)核正式發(fā)布
嵌入式LINUX系統(tǒng)內(nèi)核和內(nèi)核模塊調(diào)試

Linux內(nèi)核文件Cache機制

基于Android的Linux內(nèi)核的電源管理:概述

評論