AUTOSAR ArcticCore重構 - for_each_HOH

https://mp.weixin.qq.com/s/3f4emE-XSDI2EQGwKgL4Igexpress

 Arctic CoreAUTOSAR的實現,早期版本是開源的。數組

基本問題

ARM架構下對CAN driver的實現(arch/arm/arm_cm3/drivers/Can.c)中,有這樣一段代碼:數據結構

 

Can_Arc_Hoh是個數組,數組每一個元素含有Can_Arc_EOL標識是不是最後一個元素,最後一個元素爲0,其餘爲1架構

這裏利用指針hoh--,而後++,而後使用do while循環來遍歷數組每個元素,遍歷完Can_Arc_EOL1的元素亦即最後一個元素後結束循環。spa

 

hoh--,後++ 是一個小技巧,在這裏也很實用,不過也略顯突兀和生硬,不對應問題邏輯。do while 循環也不經常使用。可否改善呢?指針

這裏對數組元素的Can_Arc_EOL標誌的判斷,在遍歷完該數組元素以後進行,因此咱們可使用以下for循環替換:code

 

for (const Can_HardwareObjectType* hoh = canHwConfig->Can_Arc_Hoh;; hoh++) { /// do something
    if (hoh->Can_Arc_EOL) { break; } } 

這樣看比前面的do while循環,要天然一些,沒有與解決問題邏輯不相關的代碼。blog

更進一步

若是這樣的do while循環只有一處,則到此爲止。不過咱們能夠看到代碼裏有五六處這樣的寫法,若是每一處都改爲for if break的寫法,也以爲繁瑣。有沒有辦法改進呢?索引

 

若是對Linux內核代碼較熟悉,則會發現Linux kernel中的循環,多使用for_each_entry這樣的宏定義來簡化對list, hlist等數據結構的操做,使用者只需關注業務邏輯,而無需關注數據結構。get

 

咱們嘗試使用定義for_each_HOH宏來屏蔽這裏數組遍歷的細節。由於宏定義只能定義for (;;)這一段,沒法引入花括號內的代碼段,因此if break的邏輯也要在for頭裏面實現,即如:

 由於for裏面沒法使用語句,只能使用表達式,因此這裏for的第三段裏的if else編譯沒法經過。聰明的人立馬就能想到?三元表達式,能夠與if else等效。但如其名字同樣,問號三元表達式是一個表達式,而非語句,即expression, not statement. 因此代碼變成了:

#define for_each_HOH(hoh, config) \

        for (hoh = config->Can_Arc_Hoh;; \ (hoh->Can_Arc_EOL ? break : hoh++))

 這裏break不能做爲三元表達式裏的一項,編譯沒法經過。因此須要一個結束標識符,這裏有兩種方法:

 

1. 引入變量就叫__EOL,代碼以下:

#define for_each_HOH(hoh, config) \

        for (boolean __EOL = false, hoh = config->Can_Arc_Hoh; !__EOL; \ (hoh->Can_Arc_EOL ? __EOL = true : hoh++))

 能夠發現__EOLhoh->Can_Arc_EOL 有必定的對應關係,即__EOL等於剛遍歷過的數組元素的Can_Arc_EOL標誌,能夠簡化爲:

#define for_each_HOH(hoh, config) \

        for (boolean __EOL = false, hoh = config->Can_Arc_Hoh; !__EOL; \ __EOL = hoh->Can_Arc_EOL, hoh++)

 看到了意想不到的效果,三元表達式也不用了。只是最後多執行一次hoh++,還有一個假設:數組不爲空,包含至少一個元素。假設成立才能給__EOL賦初值爲false。這個假設應該是一直的假設,do while也須要這個假設。

 

因此這種方法最後多執行一次hoh++。彷佛又回到最初的do while多執行一次hoh--。不太同樣,do whilehoh--純屬小技巧。而這裏hoh++是問題邏輯的一部分,只是最後會多執行一次。

 

2. 使用現有遊標hoh做爲結束標誌,代碼以下:

#define for_each_HOH(hoh, config) \

        for (hoh = config->Can_Arc_Hoh; hoh != NULL; \ (hoh->Can_Arc_EOL ? hoh = NULL : hoh++))

 好處是不須要額外定義變量,順即可以判斷第一個元素是否爲NULL,即數組爲空也能夠成立,但這個只能看作是一個反作用,而不能做爲好處。

很差的地方是相比第一種方法多了一個判斷。

 

如何選擇呢?見仁見智。

 

最後代碼變成了以下形式:

#define for_each_HOH(hoh, config) \

        for (hoh = config->Can_Arc_Hoh; hoh != NULL; \ (hoh->Can_Arc_EOL ? hoh = NULL : hoh++)) const Can_HardwareObjectType* hoh; for_each_HOH(hoh, canHwConfig) { ///do something
 }

 

簡單明瞭,不管是五六處,仍是十幾處,只須要for_each_HOH便可,數組遍歷細節被屏蔽。

 

溯源

到這裏咱們能夠看到,這個問題其實很簡單:

  1. 最基本的數組遍歷;
  2. 每一個數組元素包含一個標識符,標識本身是否最後一個元素。

 

只能根據前一個元素的標識符來判斷是否還有下一個元素須要遍歷。

步驟以下:

  1. 拿到一個數組元素;
  2. Do something;
  3. 是否最後一個元素,若是是,結束;
  4. 若是不是,重複1

 

遍歷數組最簡單的辦法是知道數組元素的個數,而後 for (i=0; i < NUMBER; i++) 遍歷便可。

問題在於「數組大小」是否固定,若是不固定,須要約定一個存放的位置。這裏的數組大小應該是不固定的。

 

可否使用 ARRAY_SIZE 宏,即 sizeof(array)/sizeof(array[0]) 來獲取數組大小?

這裏不能夠,結構體定義時是一個指針,而非數組。

另外即便是數組,也有可能出問題。由於AUTOSAR中配置有多是POST-BUILD配置,編譯時並不知道大小和位置。

 

PS.

for_each_HOH(hoh, canHwConfig)

for_each_HOH宏定義中,須要帶hoh參數,即使C99能夠在for頭的括號中聲明變量。否則在使用for_each_HOH時,hoh變量沒法索引到,因此仍是在使用for_each_HOH的位置聲明hoh變量爲佳。

相關文章
相關標籤/搜索