位设置和代码可读性

时间:2009-07-28 22:27:31

标签: c++ embedded performance arduino

我有一个Arduino应用程序(实际上是一个库),其中包含许多状态标志 - 最初我只是将它们声明为整数(在这种情况下,uint8_t为8位无符号字符)。但我可以将它们全部合并为一个整数,并使用位掩码操作来设置和测试状态。

前者的一个例子:

if (_shift == HIGH)
{
    _shift = LOW;
}
else
{
    _shift = HIGH;
}

后者的一个例子

#define SHIFT_BIT 0

if (bitRead(_flags, SHIFT_BIT) == HIGH)
{
   bitWrite(_flags, SHIFT_BIT, LOW);
}
else
{
   bitWrite(_flags, SHIFT_BIT, HIGH);
}

前者读得更好,但后者效率更高(空间和时间)。在这种情况下,空间和时间效率是否总能获胜?或者这是一种只在需要时才会发生的优化?

(添加)

为了完整性,这里是bitWrite等宏的接线定义:

#define bitRead(value, bit) (((value) >> (bit)) & 0x01)
#define bitSet(value, bit) ((value) |= (1UL << (bit)))
#define bitClear(value, bit) ((value) &= ~(1UL << (bit)))
#define bitWrite(value, bit, bitvalue) (bitvalue ? bitSet(value, bit) : bitClear(value, bit))

9 个答案:

答案 0 :(得分:7)

在此问题上查看Raymond Chen's excellent take。总之,您需要进行一些详细的计算,以确定后一种情况是否实际上更有效,具体取决于有多少对象与实际设置这些状态的多少次调用。

就可读性而言,看起来你正在使用成员变量,这意味着你可能已经将它们封装在很好的函数中。在这种情况下,我并不关心可读性,因为至少使用该类的人的代码看起来不错。但是,如果需要关注,您可以将其封装在私有函数中。

答案 1 :(得分:5)

根据AVR-GCC编译器的合规性,我不确定,你可以做这样的事情并保持良好和干净。

struct flags {
    unsigned int flag1 : 1;  //1 sets the length of the field in bits
    unsigned int flag2 : 4;
}; 

flags data;

data.flag1 = 0;
data.flag2 = 12;

if (data.flag1 == 1)
{
    data.flag1 = 0;
}
else
{
    data.flag1 = 1;
}

如果您还想一次访问整个标志int,那么:

union 
{
    struct {
        unsigned int flag1 : 1;  //1 sets the length of the field in bits
        unsigned int flag2 : 4;
    } bits;
    unsigned int val;
} flags;

然后,您可以访问具有2个间接级别的位:variable.bits.flag1&lt; - 返回单个位标志或单个级别以获得整个int值的标志:variable.val&lt; - -returns int

答案 2 :(得分:3)

如果通过拆分为两个方法而不再使用常量HIGHLOW,则可以更清楚。只需制作bitSetbitClear方法即可。 bitSet将该位设置为HIGHbitClear将该位设置为LOW。然后它变成:

#define SHIFT_BIT 0

if (bitRead(_flags, SHIFT_BIT) == HIGH)
{
    bitClear(_flags, SHIFT_BIT);
}
else
{
    bitSet(_flags, SHIFT_BIT);
}

当然,如果您只有HIGH == 1LOW == 0,那么您不需要进行==检查。

答案 3 :(得分:1)

就我而言,即使你的后一段代码仍然可读。通过为每个标志命名,可以毫不费力地读取代码。

糟糕的方法是使用“神奇”数字:

if( _flags | 0x20 ) {  // What does this number mean?
   do_something();
}

答案 4 :(得分:1)

如果您不需要优化,请不要使用最简单的解决方案。

如果你确实需要优化,你应该知道:

  • 如果您只设置或清除该位而不是切换它,第一个版本的速度会快一些,因为这样您就不需要读取内存了。

  • 第一个版本更好w.r.t.并发性。在第二个中你有read-modify-write,所以你需要确保不同时访问内存字节。通常你会禁用中断,这会稍微增加你的中断延迟。此外,忘记禁用中断可能导致非常讨厌和难以找到错误(到目前为止我遇到的最糟糕的错误就是这种类型)。

  • 第一个版本比w.r.t更好。代码大小(闪存使用量减少),因为每次访问都是单个加载或存储操作。第二种方法需要额外的位操作。

  • 第二个版本使用较少的RAM,特别是如果你有很多这些位。

  • 如果您想一次测试多个位(例如,是设置的位之一),第二个版本也会更快。

答案 5 :(得分:1)

如果您正在谈论可读性,位集和C ++,为什么我在std::bitset上找不到任何内容?据我所知,嵌入式程序员的比赛非常适合使用位掩码,并且因其纯粹的丑陋(面具,而不是比赛:)而变得盲目,但除了掩模和位域之外,标准库也有一个非常优雅的解决方案。

一个例子:

#include <bitset>

enum tFlags { c_firstflag, c_secondflag, c_NumberOfFlags };

...

std::bitset<c_NumberOfFlags> bits;

bits.set( c_firstflag );
if( bits.test( c_secondflag ) ) {
  bits.clear();
}

// even has a pretty print function!
std::cout << bits << std::endl;// does a "100101" representation.

答案 6 :(得分:0)

对于位字段,最好使用逻辑运算,因此您可以这样做:

if (flags & FLAG_SHIFT) {
   flags &= ~FLAG_SHIFT;
} else {
   flags |= FLAG_SHIFT;
}

现在看起来前者具有后者的效率。现在你可以有宏而不是函数,所以(如果我有这个权利 - 它会是这样的):

#define bitIsSet(flags,bit) flags | bit
#define bitSet(flags,bit) flags |= bit
#define bitClear(flags,bit) flags &= ~bit

您没有调用函数的开销,代码再次变得更具可读性。

我还没有和Arduino玩过(但是)但是我可能已经知道了这类东西的宏,我不知道。

答案 7 :(得分:0)

我想说我关心的第一件事是: “#define SHIFT 0” 为什么不使用常量而不是宏?就效率得到而言,常数允许确定类型,从而确保之后不需要转换。

至于技术效率: - 首先,摆脱else子句(如果其值已经为高,为什么将该位设置为HIGH?) - 第二,首先要有可读性,内部使用位屏蔽的内联setter / getter可以完成工作,高效且可读。

至于存储,对于C ++,我倾向于使用bitset(结合枚举)。

答案 8 :(得分:0)

说起来太简单了:

flags ^= bit;