John Lee(1513562323) 22:05:30
好了,耽误大家了,我们最后讨论一下代码效率。
John Lee(1513562323) 22:05:51
在 baud_t 类型中,有 3 个成员:
John Lee(1513562323) 22:06:01
volatile uint32_t& ref;
uint32_t val;
uint32_t changed;
John Lee(1513562323) 22:06:24
在各个“位对象”的类型(sfb_t<baud_t, uint32_t, S, W>)的 write() 成员函数中,也有一大堆运算,有若干变量,还涉及指针。
John Lee(1513562323) 22:06:50
猛一看,效率肯定不高。
John Lee(1513562323) 22:07:32
但实际上不是那样的,C/C++ 中,有一个很重要的编译优化规则:常量叠算。
John Lee(1513562323) 22:07:46
【编译器优化小知识】:常量叠算(Constant folding):一种编译器的优化技巧,指在编译时就对常量表达式进行预求值。
John Lee(1513562323) 22:11:39
例如:
int arg = ((5 + 2) * 8 * 9) >> 10; // 一堆常量运算,这些常量有可能是宏定义
foo(arg); // 函数调用,编译器会直接把 arg 实参进行“常量叠算”后,以求得的“常数”作为实参调用foo()。
John Lee(1513562323) 22:13:14
让我们来分析一下“UART0.BAUD().BRD(20).DIV_X_ONE(1);”整个过程中用到的各种数据:
John Lee(1513562323) 22:13:38
“临时对象”的类型成员:
volatile uint32_t& ref:是硬件寄存器的引用,逻辑上是“常量指针”。
uint32_t val:硬件寄存器的数据缓冲,是“变量”。
uint32_t changed:由于初始值以及其后的操作数据,都是常量的,所以,本身也是“常量”。
John Lee(1513562323) 22:14:20
“临时对象”的 this 指针:“临时对象”的 this,C++ 认为是“常量”(在生成后,没有任何赋值或转移等操作)。
John Lee(1513562323) 22:16:20
write() 函数中用到的数据:
“位对象”this 指针:这个“值”与“临时对象”的 this相等(上次群课说过),由于“临时对象”的 this 是常量,所以它也是“常量”。
register baud_t* p:这个值就是 this 强制而来的“register baud_t* p = reinterpret_cast<baud_t*>(this);”,是“常量”。
register auto _v:这个值是就是形参 v“register auto _v = decltype(p->val)(v);”,如果形参 v 是常量,那么这个也是“常量”。
John Lee(1513562323) 22:16:56
好了,所有的这些数据中,只有“临时对象”的类型成员 val 是真正的变量,其它的都是常量。
John Lee(1513562323) 22:18:08
最后,编译器只会对 val 的分配空间,而直接求出各个常量的值,作为 CPU 指令的立即数。
John Lee(1513562323) 22:19:27
下面,我们看看编译器对这个表达式“UART0.BAUD().BRD(20).DIV_X_ONE(1);”的编译结果:
John Lee(1513562323) 22:20:43
ldr r3,=0x40050000 // 常数:UART0 地址
ldr r1,=0x10000014 // 常数:BRD:20, DIV_X_ONE:1
ldr r2,[r3,#36] // 取 BAUD 寄存器值到缓冲数据
lsr r0,r2,#16 // 缓冲数据清除 BRD 位域中的原数据
lsl r2,r0,#16
orr r2,r1 // 缓冲数据“或”常数
str r2,[r3,#36] // 缓冲数据写回到 BAUD 寄存器
John Lee(1513562323) 22:23:19
可以看到,缓冲数据被分配在了 CPU 寄存器中,而其它的数据都没有直接地分配空间。
John Lee(1513562323) 22:24:13
由于 ARM 指令的限制,常量也临时性地占用了 CPU 寄存器。
John Lee(1513562323) 22:27:29
我们可以看到,编译器生成的代码,效率是非常高的,同时,源程序也是非常优雅和安全的。
murex(344582199) 22:27:47
嗯,非常强悍
John Lee(1513562323) 22:30:55
这个帖子:https://bbs.21ic.com/icview-295372-2-1.html#pid2046953,讨论了 C 访问硬件寄存器的方法。
John Lee(1513562323) 22:31:28
也谈到了 C++ 访问硬件寄存器的“使用方法”,但没有谈实现原理。
John Lee(1513562323) 22:32:30
这几次的群课,便是比较充分地讨论了“实现原理”。
murex(344582199) 22:34:19
嗯,是比较详细了
John Lee(1513562323) 22:34:30
算是全部完成了。
CountryMan(176419557) 22:54:48
比较大型的嵌入式项目吧
John Lee(1513562323) 22:55:04
强迫你在项目中,按C++的思想来设计。
日期:2012/1/10
John Lee(1513562323) 22:55:35
从总体到各个细节,逐级抽象。
CountryMan(176419557) 22:55:56
抽象真不懂
John Lee(1513562323) 22:56:28
抽象,是软件设计中永恒的话题。
M0小白菜(465834115) 22:56:55
思路 ?说到思路 我现在编程是没什么思路的 就是想到哪就写到哪 ?
John Lee(1513562323) 22:57:56
抽象,就是按面向对象的观点,来分析和归类项目中模块,和各个模块之间的关系。
CountryMan(176419557) 22:58:52
这种好想还有个专门的岗位呀
CountryMan(176419557) 22:58:56
好像
CountryMan(176419557) 22:59:07
系统工程师?
John Lee(1513562323) 22:59:15
抽象实际上项目架构设计师的工作。
CountryMan(176419557) 23:00:11
哦
CountryMan(176419557) 23:00:24
这个要求忒高吧
John Lee(1513562323) 23:01:35
架构设计师,不仅要对项目的软件进行合理的抽象,而且要对硬件上影响抽象的不合理的地方,对硬件设计人员提出要求。
CountryMan(176419557) 23:03:10
这个暴强
John Lee(1513562323) 23:03:21
甚至于,硬件架构的总体设计都需要软件架构设计师参与。
John Lee(1513562323) 23:04:53
而要做到这些,你就要从学会“抽象”入手。
道可道(549040622) 23:06:45
抽象好难
道可道(549040622) 23:07:03
中国人缺少抽象思维
CountryMan(176419557) 23:07:10
是啊
老师到时候多举例子
CountryMan(176419557) 23:08:43
怎样去培养抽象思维呢
M0小白菜(465834115) 23:08:59
同上
John Lee(1513562323) 23:10:47
一个好的“抽象”,不仅能设计出稳定的系统,重要的是,降低“模块”之间的耦合性,使各个模块的软件能够相对独立,这样当系统设计变更,增加或减少的模块时,才具有良好的“弹性”,不至于做出大的修改或推倒重来。
CountryMan(176419557) 23:12:47
低耦合
俺目前就只能理解到这里
John Lee(1513562323) 23:12:57
而且,这些相对独立的模块,可以形成文档和程序库,当以后的项目中,含有类似的模块时,可以立即使用。
CountryMan(176419557) 23:15:00
这个层次就高了
日期:2012/1/10
John Lee(1513562323) 23:15:32
道可道(549040622) 23:11:51
李老师,是不是这个得靠经验呢
--------------
是的,任何事情都是经验,知识本身就是对经验的“抽象”。
道可道(549040622) 23:17:53
所以,大量的练习,练习多了,就理解了吧
CountryMan(176419557) 23:18:11
有没有速成法
John Lee(1513562323) 23:18:55
多实践,多总结。总结就是“抽象”,把经验“抽象”为知识。
John Lee(1513562323) 23:19:37
特别是要多参与大型项目的开发。
John Lee(1513562323) 23:20:29
了解系统的运行原理,即使不是你来设计,你也可以把它作为练习。
John Lee(1513562323) 23:23:04
还有,就是对一些 MCU 的常用外设,你也可以去练习“抽象”。
CountryMan(176419557) 23:23:21
比如USART
CountryMan(176419557) 23:23:25
怎样去抽象呢
CountryMan(176419557) 23:23:59
他有属于自己的东西
波特率啥的
John Lee(1513562323) 23:24:20
抽象的目的是形成重用性良好的软件模块。
John Lee(1513562323) 23:24:58
你要按着这个目的去抽象。
道可道(549040622) 23:25:46
等工作去,还是打基础吧
(来自手机QQ: http://mobile.qq.com/v/ )
CountryMan(176419557) 23:25:57
可不可以简单理解为让人容易调用
John Lee(1513562323) 23:26:14
这个是一方面
CountryMan(176419557) 23:26:15
API那样的接口函数
John Lee(1513562323) 23:26:49
是的
CountryMan(176419557) 23:28:25
哦
他算是抽象的一部分
然后系统就是这样各个API的集合
John Lee(1513562323) 23:28:27
除了重用性外,适用性、易用性和效率都是很重要的。
CountryMan(176419557) 23:28:35
嗯
John Lee(1513562323) 23:29:57
一个系统,当然有它自己的、与其他系统不同的私有属性。
CountryMan(176419557) 23:30:10
嗯
John Lee(1513562323) 23:30:26
不能简单说,是各个API的集合。
CountryMan(176419557) 23:31:00
俺是肤浅理解
CountryMan(176419557) 23:31:06
嗯
John Lee(1513562323) 23:32:09
就像PC一样,操作系统(windows, linux)提供给了我们API和各种库,但我们的程序还是要靠我们自己来写。
CountryMan(176419557) 23:32:35
还要自己去抽象
John Lee(1513562323) 23:32:39
对
CountryMan(176419557) 23:33:30
这个难度巨大
CountryMan(176419557) 23:34:38
目前只能很肤浅的去理解这个抽象
日期:2012/1/10
John Lee(1513562323) 23:35:17
操作系统只能提供给我们一些重用性很高的程序库,但我们系统还要加上的私有软件逻辑。
John Lee(1513562323) 23:36:18
实际上,各种软件都是这样形成的。
CountryMan(176419557) 23:36:25
MCU的官方LIB也算是一种底层的抽象吧
John Lee(1513562323) 23:36:45
是的
CountryMan(176419557) 23:37:19
哦
John Lee(1513562323) 23:38:16
一方面它避免了我们之间操作寄存器的麻烦,另一方面,可以是我们的程序在各个不同的 MCU 之间具有一定的移植性。
John Lee(1513562323) 23:39:22
抽象也不是只有一个层次,而是多层次的。
John Lee(1513562323) 23:40:19
每个层次都依赖其它层次的抽象成果。
CountryMan(176419557) 23:40:31
嗯
那有没有一个最高级的抽象
如果违背了它的抽象特点就出现比较大的问题呢
对系统的编写或者其他
CountryMan(176419557) 23:40:53
因为系统必须有核心
同样抽象是不是也需要呢
John Lee(1513562323) 23:41:12
?
CountryMan(176419557) 23:41:45
就类似是一个最核心的抽象
任何抽象都由它派生而得
John Lee(1513562323) 23:42:05
有的。
CountryMan(176419557) 23:42:09
就是类似一个由主到次的设计过程
John Lee(1513562323) 23:42:52
这是一个在层次上相互依赖的体系。
CountryMan(176419557) 23:43:06
哦
John Lee(1513562323) 23:44:02
比如 UART,就 FIFO 这一个小小的机构,也可以抽象。
John Lee(1513562323) 23:44:50
有些 UART 是没有 FIFO 的,这个抽象出来就是一个层次。
CountryMan(176419557) 23:45:07
怎样抽象呢
CountryMan(176419557) 23:45:11
俺就知道队列
John Lee(1513562323) 23:45:50
而另一些 UART 是有 FIFO 的,这个抽象又是一个层次。
CountryMan(176419557) 23:46:12
但是它就是属于USART独有
这样如果系统有多个类似这样的FIFO
感觉代码量就增加了
John Lee(1513562323) 23:46:44
因为除了 FIFO 这一个机构外,UART 其它的部分,相同的地方很多。
CountryMan(176419557) 23:46:58
好像OS的控件就是干这个活的
John Lee(1513562323) 23:48:13
如果你为没有 FIFO 的 UART,和具有 FIFO 的 UART 都各自抽象,而没有层次关系的话,那么它们之间的代码就没有重用性。
CountryMan(176419557) 23:48:42
是的
CountryMan(176419557) 23:49:01
老师有没有这样的例程呀
John Lee(1513562323) 23:49:34
正确的抽象方法应该是,先抽象没有 FIFO 的 UART,形成一个模块。
John Lee(1513562323) 23:50:15
然后在“继承”这个模块,抽象出具有 FIFO 的 UART 模块。
CountryMan(176419557) 23:51:08
哦
但是假如UART具有这个FIFO
是不是就不需要这个抽象的FIFO呢
日期:2012/1/10
John Lee(1513562323) 23:51:54
就是说,没有 FIFO 的 UART 是这个抽象层次中的“底层”,而具有 FIFO 的 UART 是“高层”,它依赖“底层”。
CountryMan(176419557) 23:52:46
哦
John Lee(1513562323) 23:53:04
除了它私有的 FIFO 属性外,对于 UART 其它的属性,它重用着“底层”的属性代码。
CountryMan(176419557) 23:54:28
有点明白
从无到有
从有到完善
John Lee(1513562323) 23:54:54
这样,你就不必写“两套”除了 FIFO 外,代码都相同的程序。
CountryMan(176419557) 23:55:21
嗯
MCS8098(285216815) 23:55:48
老师 这就是 传说中的 继承 封装 多态吗
John Lee(1513562323) 23:56:13
而只对“高层”的、具有私有属性的抽象,写出私有代码即可。
CountryMan(176419557) 23:56:14
嗯
应该是继承的一种
CountryMan(176419557) 23:57:39
模板是不是抽象的一个实例
John Lee(1513562323) 23:57:42
嗯,你们理解得很快。
John Lee(1513562323) 23:58:13
好了,该休息了。
|