就像在linux內核源代碼中一樣,我發現了很多以內聯形式實現的微小功能的函數,還有許多其他功能也在MACRO中實現。是否有任何標準來決定在Linux內核中通過內聯還是宏來實現函數?
所以我想知道在決定選擇哪一個時考慮的是什麼因素,因爲我認爲這兩者都是合適的。或者這只是個人風格?
就像在linux內核源代碼中一樣,我發現了很多以內聯形式實現的微小功能的函數,還有許多其他功能也在MACRO中實現。是否有任何標準來決定在Linux內核中通過內聯還是宏來實現函數?
所以我想知道在決定選擇哪一個時考慮的是什麼因素,因爲我認爲這兩者都是合適的。或者這只是個人風格?
宏不會進行類型檢查並需要其他特殊的考慮(即只對一個表達式求值一次),所以應儘可能避免它們。但是不檢查類型的優點是允許您在C89中編寫通用函數。
也編寫實際的inline
函數允許(使用一個可以在內核配置中激活的特定標誌)編譯器也可以將其取消,如果這具有性能優勢,而這對於宏來說是不可能的,作爲簡單的文本替換。
作爲文本替換的宏可以做的不僅僅是內聯函數。他們也可能有不必要的副作用。考慮衆所周知的交換宏。
#define SWAP(a,b) {int t=a; a=b; b=t}
並指出SWAP(i,t[i])
做不必要的事情。
內聯函數具有函數調用類似語義(參數被評估一次等)的優勢。
所以它主要是編碼風格的問題,並認識到宏功能是不同種類的動物。
而你的問題並沒有真正與內核相關。任何C(甚至C++)程序都有相同的問題。
請記住,Linux內核中的很多代碼已經存在很長一段時間了。一些宏也會對輸入/環境的其他部分做出「聰明的東西」,而不是函數會/可能/應該做的。
作爲一般規則,儘可能使用(內聯)函數,因爲如上所述,功能的「疑難問題」較少。
如果你做了巧妙的替換或者其他的「只能在宏中做」,那麼在可能的情況下生成一個形成函數參數的宏是非常好的。例如。
#define ASSERT(x) do { if (!(x)) { fprintf(stderr, "Assertion failed on %s:%d", __FILE__, __LINE__); exit(1); } while(0);
可以做的:
#define ASSERT(x) do_assert(!!(x), __FILE__, __LINE__);
void do_assert(bool val, const char *file, int line)
{
if (!val)
{
fprintf(....);
exit(1);
}
}
現在你可以,例如,在調試版本,在您的斷言設置一個斷點,如果裏面,每當你的斷言失敗了,你可以看到你如何到達那裏。有了宏,這是不可能的。
現代編譯器在適當的情況下做了很好的內聯操作,給了一半的機會。
由於內核使用了大量的GCC擴展,如_____attribute_____等。所以我認爲副作用因素不能在考慮範圍內,因爲**語句表達式**可以處理這個問題。請參閱http://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Statement-Exprs.html#Statement-Exprs – larmbr
不,GCC擴展並不總是有幫助的(即使它們非常方便) ,正如我簡單的'SWAP'示例所示。 –
至於副作用的東西,我認爲海灣合作委員會擴展是勝任的。但我同意你的看法,主要是編碼風格。 – larmbr