本帖最后由 john_lee 于 2011-12-17 02:00 编辑
嗯,不错,各有各的观点,欢迎讨论。
在大程序中,使用C++是有优势的,这一点貌似大家都认同。
那我们就往程序的细节方向讨论,看看C++是否还有优势。
在单片机中,特殊功能寄存器(SFR, Special Function Register)的访问可谓是非常细节了,我们知道,RISC架构的CPU,程序要修改一个SFR中的某一个或某几个特殊功能位域(SFBF, Special Function Bit Field)时,需要将整个SFR读到CPU寄存器中,在CPU寄存器中修改完毕后,再写回到SFR,这就是所谓的“读-修改-写”过程。
那么,使用C语言应该如何来描述这个操作?
一般有两种方法:
1、位操作(与,或,反,异或)
例如:对SFR中的bit2, bit5置1,一般的写法大致如下:#define SFB_LED 0x04 // 或(1 << 2)
#define SFB_RELAY 0x20 // 或(1 << 5)
SFR |= SFB_LED | SFB_RELAY;
可以看出,位操作是以整个SFR为目标的赋值操作,表达式右值就是SFR,编译器生成的代码会先“读”,然后“修改”所有需要修改的位,最后“写”,所以代码效率是最高的,这是位操作的优点。但是,SFB_LED, SFB_RELAY等宏代表的SFBF,与SFR在语法逻辑上没有任何关系,编译器不可能知道这些SFB_LED, SFB_RELAY是属于这个SFR的,如果你错写成了另外的宏(数值定义),编译照样通过。这个属于安全性问题。至于算不算缺点,那就要看有没有其它方法,使编译器可以检查SFR和SFBF的关系。如果没有,那么位操作就是目前最好的方法了;否则,安全性差就是位操作的缺点。
幸运的是,有方法,那就是位域。
2、位域
我们还是以上面的例子,但是用位域的方式实现:struct sfr {
uint32_t : 2;
volatile uint32_t SFB_LED : 1;
uint32_t : 2;
volatile uint32_t SFB_RELAY : 1;
};
extern struct sfr SFR;
SFR.SFB_LED = 1;
SFR.SFB_RELAY = 1;
使用位域的方式来定义SFBF,利用了语言本身的作用域的约束检查,编译器可以明确地知道SFB_LED,SFB_RELAY是属于SFR的,当你写错时,编译器一定不会任你错下去,这正好克服了位操作安全性差的缺点。
但是,我们还注意到了,位域的操作,不再是以整个SFR为目标了,而是以其中的某个位域为目标,而位域的定义,由于是SFBF,为了保证操作的确定性以及防止优化,所以,一般需要加上volatile修饰,这样的结果是,程序每修改一个SFBF,编译器就会生成一个“读-修改-写”的过程。
如果我们的程序修改了同一个SFR中的两个SFBF,按刚才的规则,编译器将会生成两个“读-修改-写”,但我们可能不需要这两个SFBF有一定的先后顺序(甚至需要两个SFBF同时操作),而希望编译器把两个“读-修改-写”合并为一个。但是我们失望了。
现在,我们来做个小结:
SFBF操作方式: | 位操作 | 位域 | 效率: | 高 | 低 | 安全性: | 差 | 好 |
看来,鱼和熊掌,不可兼得啊。
接下来,试试C++。
我们定义了一堆类,模板,重载操作符等等(实现细节就省了,否则两屏幕都写不完)。
展示一下用法: // 等效的C表达式为:
SFR().SFB_LED(1).SFB_RELAY(1); // SFR |= SFB_LED | SFB_RELAY;
SFR().SFB_LED(0).SFB_RELAY(0); // SFR &= ~(SFB_LED | SFB_RELAY);
SFR().SFB_RELAY(0).SFB_LED(1); // SFR = (SFR | SFB_LED) & ~SFB_RELAY;
SFR(0).SFB_LED(1); // SFR = SFB_LED;
SFR(~0).SFB_LED(0); // SFR = ~SFB_LED;
SFR(0); // SFR = 0;
如果嫌可读性不好,那么可以这样:SFR()
.SFB_LED(1)
.SFB_RELAY(1);
C++的SFR访问方法,不仅安全性同位域一样有保证,效率也同位操作一样高效,并且写法也很优雅。
总结了:
SFBF操作方式: | C位操作 | C位域 | C++ | 效率: | 高 | 低 | 高 | 安全性: | 差 | 好 | 好 |
|