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

Arctic Core是AUTOSAR的实现,早期版本是开源的。

基本问题

在ARM架构下对CAN driver的实现(arch/arm/arm_cm3/drivers/Can.c)中,有这样一段代码:

Can_Arc_Hoh是个数组,数组每个元素含有Can_Arc_EOL标识是否是最后一个元素,最后一个元素为0,其他为1。

这里利用指针hoh先--,然后++,然后使用do while循环来遍历数组每一个元素,遍历完Can_Arc_EOL为1的元素亦即最后一个元素后结束循环。

hoh先--,后++ 是一个小技巧,在这里也很实用,不过也略显突兀和生硬,不对应问题逻辑。do while 循环也不常用。能否改善呢?

这里对数组元素的Can_Arc_EOL标志的判断,在遍历完该数组元素之后进行,所以我们可以使用如下for循环替换:

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

这样看比前面的do while循环,要自然一些,没有与解决问题逻辑不相关的代码。

更进一步

如果这样的do while循环只有一处,则到此为止。不过我们可以看到代码里有五六处这样的写法,如果每一处都改成for if break的写法,也觉得繁琐。有没有办法改进呢?

如果对Linux内核代码较熟悉,则会发现Linux kernel中的循环,多使用for_each_entry这样的宏定义来简化对list, hlist等数据结构的操作,使用者只需关注业务逻辑,而无需关注数据结构。

我们尝试使用定义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++)) 

可以发现__EOL与hoh->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 while的hoh--纯属小技巧。而这里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=; i < NUMBER; i++) 遍历即可。

问题在于“数组大小”是否固定,如果不固定,需要约定一个存放的位置。这里的数组大小应该是不固定的。

能否使用 ARRAY_SIZE 宏,即 sizeof(array)/sizeof(array[]) 来获取数组大小?

这里不可以,结构体定义时是一个指针,而非数组。

另外即使是数组,也有可能出问题。因为AUTOSAR中配置有可能是POST-BUILD配置,编译时并不知道大小和位置。

PS.

for_each_HOH(hoh, canHwConfig)

for_each_HOH宏定义中,需要带hoh参数,即便C99可以在for头的括号中声明变量。不然在使用for_each_HOH时,hoh变量无法索引到,所以还是在使用for_each_HOH的位置声明hoh变量为佳。

AUTOSAR ArcticCore重构 - for_each_HOH的更多相关文章

  1. ArcticCore重构-问题列表1

    基于官方arc-stable-9c57d86f66be,AUTOSAR版本3.1.5 基本问题 Arctic Core中的代码组织有很多有待改进的地方,这里先提出几点: 1. 头文件引用混乱,所有头文 ...

  2. ArcticCore重构-VALIDATE_%

    基于官方arc-stable-9c57d86f66be,AUTOSAR版本3.1.5 基本问题 Arctic Core中使用了VALIDATE, VALIDATE_RV, VALIDATE_NO_RV ...

  3. ArcCore重构-目标文件结构化

    基于官方arc-stable-9c57d86f66be,AUTOSAR版本3.1.5   基本问题 3. 编译系统中所有代码文件通过搜索路径(VPATH)中搜索,存在名称污染问题,需加入路径信息:   ...

  4. ArcCore重构-头文件引用问题的初步解决

    基于官方arc-stable-9c57d86f66be,AUTOSAR版本3.1.5   基本问题 1. 头文件引用混乱,所有头文件通过从搜索路径(-I)中引用,存在名称污染问题,需加入路径信息:   ...

  5. 记一次.NET代码重构

    好久没写代码了,终于好不容易接到了开发任务,一看时间还挺充足的,我就慢慢整吧,若是遇上赶进度,基本上直接是功能优先,完全不考虑设计.你可以认为我完全没有追求,当身后有鞭子使劲赶的时候,神马设计都是浮云 ...

  6. CSharpGL(17)重构CSharpGL

    CSharpGL(17)重构CSharpGL CSharpGL用起来我自己都觉得繁琐了,这是到了重构的时候. 下载 CSharpGL已在GitHub开源,欢迎对OpenGL有兴趣的同学加入(https ...

  7. ASP.NET MVC5+EF6+EasyUI 后台管理系统(58)-DAL层重构

    系列目录 前言:这是对本文系统一次重要的革新,很久就想要重构数据访问层了,数据访问层重复代码太多.主要集中增删该查每个模块都有,所以本次是为封装相同接口方法 如果你想了解怎么重构普通的接口DAL层请查 ...

  8. ASP.NET MVC5+EF6+EasyUI 后台管理系统(59)-BLL层重构

    系列目录 前言:  这应该是本系统最后一次重构,将重构BLL层和Model层.来完全取代代码生成器生成的BLL层和DAL层.完全废掉了代码生成器的DAL,BLL,MODEL层.  全自动生成增,删,改 ...

  9. 原生JS实现全屏切换以及导航栏滑动隐藏及显示——重构前

    思路分析: 向后滚动鼠标滚轮,页面向下全屏切换:向前滚动滚轮,页面向上全屏切换.切换过程为动画效果. 第一屏时,导航栏固定在页面顶部,切换到第二屏时,导航条向左滑动隐藏.切换回第一屏时,导航栏向右滑动 ...

随机推荐

  1. Leetcode_107_Binary Tree Level Order Traversal II

    本文是在学习中的总结,欢迎转载但请注明出处:http://blog.csdn.net/pistolove/article/details/41964067 Given a binary tree, r ...

  2. 中国梦&nbsp;&nbsp;每个农大人的梦

    历经百年风霜,苦经岁月沧桑.农大,一个中原沃土上生长起来的大树,它在用它那不倒的生命力展示着农大的顽强与坚持,而这份苍劲和顽强,却来自于每个农大人,来自于他们的梦想,来自于他们的坚持,来自于他们的努力 ...

  3. Cocos2D物理碰撞不按预期工作的排查工作

    如果该碰撞的节点不碰撞或反过来不该碰的碰撞了,你可以检查一下几个方面: 1.对应2个节点的分类和掩码必须匹配.如果它们应该碰撞则一个节点的分类应该在另一个节点的掩码中,反之亦然. 2.注意空的分类和掩 ...

  4. Leetcode_62_Unique Paths

    本文是在学习中的总结,欢迎转载但请注明出处:http://blog.csdn.net/pistolove/article/details/43404205 A robot is located at ...

  5. 如何成为Android高手

    要成为Android 高手并不是一件容易的事情.并不是很多人想象的 能够飞快的写出几行漂亮的代码去解决一些困难的问题 就是Android 高手了.真正的Android 高手需要考虑的问题远远不是写些漂 ...

  6. mongodb系列之---副本集配置与说明

    在配置副本集之前,我们先来了解一些关于副本集的知识. 1,副本集的原理 副本集的原理与主从很相似,唯一不同的是,在主节点出现故障的时候,主从配置的从服务器不会自动的变为主服务器,而是要通过手动修改配置 ...

  7. 浅析数据结构中栈与C实现

    最近在搞摄像头驱动,o()︿︶)o 唉,别提有多烦,一堆寄存器就有人受的了--特么这不是单片机的开发,这是内核驱动开发-- 今天放松一下,我们来看看数据结构中的栈,这节的知识点可以说是数据结构中最容易 ...

  8. iOS监听模式系列之推送消息通知

    推送通知 和本地通知不同,推送通知是由应用服务提供商发起的,通过苹果的APNs(Apple Push Notification Server)发送到应用客户端.下面是苹果官方关于推送通知的过程示意图: ...

  9. 关机充电如何实现短按pwrkey灭屏

    目前关机充电PWRKEY实现长按开机和短按亮屏功能,灭屏是根据BL_SWITCH_TIMEOUTS时间,自动灭屏的:如果需要实现PWRKEY主动灭屏,请按照如下方法修改:     alps/media ...

  10. HBase学习资源

    教程 <HBase.Administration.Cookbook>  中文版<HBase管理指南> <HBase in action> <HBase权威指南 ...