前言
為什麼c語言使用function在傳參數的時候,如果傳入一個array會無法正確得到array大小呢?
這其實跟array decay有關,在linux kernel中為了防呆會加上一些code來防止產生這樣的問題(透過在compile time就產生error),接下來從array size該如何取得,到linux kernel的macro來簡單介紹。
array size & sizeof()
在寫c的時候,如果想知道一個array的大小可以這樣寫
1 | int main() |
可是當arr不是一個array,而是一個指向該array的pointer時(pointer to array),就會有問題。由於pointer的大小是4 or 8 bytes(取決於OS是32bit或64bit),所以除以一個array(arr[0])的話就會得到不一樣的結果,例如下面就會得到2而不是3。
1 | int main() |
這個問題如果平常沒注意很容易發生,比方說傳了一個pointer進去給function當作參數,在呼叫function時,實際上function的會先創造一個指標變數再去紀錄pointer的內容,所以就會因為上面的理由得到不正確的陣列大小。
在linux kernel內為了有如下的macro(include/linux/kernel.h),定義了如何取得array size:
1 |
可以注意到他還有多一個_must_be_array(arr)
,這是用來確保傳入的arr一定是個array而不是pointer to array,__must_be_array
的定義如下:
1 | /* &a[0] degrades to a pointer: a different type from an array */ |
先來看_same_type在幹嘛再回來理解這一句:
1 |
__builtin_types_compatible_p
是gcc的extension,有興趣的請再自己往下追,總之它會比較兩個variable type,如果相同回傳1,不同則回傳0。
所以__must_be_array
就是比較a和&(a)[0]的type是不是相同:
- 如果a是array,&(a)[0]會被degrade成pointer,_same_type會回傳1
- 如果a是pointer,&(a)[0]仍然是一個pointer,_same_type會回傳0
接下來來看BUILD_BUG_ON_ZERO在幹嘛:1
- 對e做兩次not,確保e一定會是0或1
- 然後乘上-1,確保e一定會是0或-1
- 透過bit-field的技巧宣告一個包含int的struct,詳細可以參考超連結
當e不是0的時候,這一個macro就會產生一個包含-1個bit的struct,不過沒有這種東西,所以在compile time就會產生錯誤。
好了,追了那麼多code,回頭重看一次,在linux kernel的ARRAY_SIZE macro
為了預防傳入的變數是pointer而不是真正的array,背後其實花了很多心力來防範這件事,使得如果發生問題就會在編譯時期就失敗,避免了因為誤用而在run-time才發生問題的狀況。
最近開始慢慢接觸linux code,真的覺得越來越不懂c了呢。
Reference
- TIL: ARRAY_SIZE in Linux kernel
- Linux Kernel: BUILD_BUG_ON_ZERO() / BUILD_BUG_ON_NULL()
- Linux Kernel: ARRAY_SIZE()
- C++ - array decay (C++軟體開發 - 陣列衰變 概念與實例)