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变量为佳。

相关文章
相关标签/搜索