Linux Kernel 程式碼藝術——編譯時斷言

發表於2016-11-05

本系列文章主要寫我在閱讀Linux核心過程中,關注的比較難以理解但又設計巧妙的程式碼片段(不關注OS的各個模組的設計思想,此部分我準備寫在“深入理解Linux Kernel” 系列文章中),一來通過核心程式碼複習一下C語言及組合語言的語法,二來學習核心開發大牛們書寫程式碼的風格及思路。

在核心檔案 include/linux/bug.h中,有下面兩行的巨集定義:

以第一個分析,它表示的是:檢查表示式e是否為0,為0編譯通過且返回0;如果不為0,則編譯不通過。

可能從這個巨集的名字上看可能容易理解錯,或者改為“BUILD_BUG_OR_ZERO”更好,關於這個的討論有人也提交這個patch(http://lkml.indiana.edu/hypermail/linux/kernel/0703.1/1546.html),但未能被社群接受。我們且不管這個巨集定義名字怎樣,來逐步分析一下這個巨集是如何來實現的:

sizeof(struct { int : –!!(e); } ))

1. (e): 表示式e的宣告

2. !!(e): 對e的結果進行兩次求非。即如果e開始是0的話,結果就是0;如果e不為0,則結果為1。

3. –!!(e): 再乘以-1。如果第2步結果為0,則仍為0;否則結果為-1。

4. struct { int : –!!(0); }  –>  struct { int : 0; } : 如果e的結果為0,則我們宣告一個結構體擁有一個int型的資料域,並且規定它所佔的位的個數為0。這沒有任何問題,我們認為一切正常。

5. struct { int : –!!(1); }  –>  struct { int : –1; } : 如果e的結果非0,結構體的int型資料域的位域將變為一個負數,將位域宣告為負數這是一個語法的錯誤。

6. 現在要麼結果為宣告瞭一個位域為0的結構體,要麼出現位域為負數編譯出錯;如果能正確編譯,然後我們對該結構體進行sizeof操作,得到一個型別為size_t的結果,值為0。

再總結一下,BUILD_BUG_ON_ZERO(e) 表示的就是若表示式e結果為0,則編譯通過,該巨集的值也為0;若表示式e的結果不為0,則編譯不通過。

這會讓人聯想到C語言中 assert 巨集的用法:

void assert(int expression);

如果引數expression 計算的結果為0,它先向stderr列印一條出錯資訊,然後通過呼叫 abort 來終止程式執行;否則斷言成立,繼續執行。

我們討論的巨集與assert本質區別在於,我們的巨集在編譯時進行測試,而assert巨集是在執行時測試。我們希望能儘早地捕獲到我們編譯時的錯誤,而不是推遲到執行時。我管這種巨集用法叫做“編譯時斷言”,assert為“執行時斷言”。

理解了上面之後,再來看看第二個BUILD_BUG_ON_NULL(e)巨集,與第一個類似,用來在編譯時斷言e是否為NULL,若是這個巨集返回(void *)0 (即NULL,與第一個巨集的區別);不為NULL時編譯出錯。

除了上面的兩個編譯時斷言之外,include/linux/bug.h檔案中還有另幾個大家可以思考表示何意,如:

含義可以參考檔案中巨集定義的註釋說明。

參考資料:

http://blog.csdn.net/jiyucn/article/details/862085   C語言中關於結構體位域的詳細說明

http://blog.csdn.net/jiyucn/article/details/862062   C語言中sizeof相關問題

http://www.cplusplus.com/reference/cassert/assert/   assert用法說明

http://stackoverflow.com/questions/9229601/what-is-in-c-code   問題及解答均來源於Stackoverflow

相關文章