4

我有一个 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 等宏的 Wiring 定义:

#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))
4

9 回答 9

7

看看Raymond Chen在这个问题上的出色表现。总之,你需要做一些详细的计算来确定后一种情况是否实际上更有效,这取决于有多少对象与实际设置这些状态的调用点有多少。

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

于 2009-07-28T22:34:45.230 回答
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<--returns 单个位标志或使用单个级别来获取整个 int 值的标志:variable.val<--returns int

于 2009-07-28T23:07:56.397 回答
3

如果您不再需要使用常量HIGHLOW,通过拆分为两种方法会更清楚。只是制作bitSetbitClear方法。bitSet将该位设置为HIGH,并将bitClear该位设置为LOW。然后变成:

#define SHIFT_BIT 0

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

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

于 2009-07-28T22:36:06.070 回答
1

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

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

  • 如果您只设置或清除该位而不是切换它,则第一个版本的速度会最低限度,因为您不需要读取内存。

  • 第一个版本更好的并发性。在第二个你有读-修改-写,所以你需要确保内存字节不是同时访问的。通常你会禁用中断,这会增加你的中断延迟。此外,忘记禁用中断可能会导致非常讨厌且难以找到的错误(到目前为止我遇到的最讨厌的错误就是这种错误)。

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

  • The second version is uses less RAM, especially if you have a lot of these bits.

  • The second version is also faster if you want to test several bits at once (e.g. is one of the bits set).

于 2009-07-29T07:18:43.557 回答
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.
于 2009-07-29T07:37:04.907 回答
1

在我看来,即使您的后一个代码仍然非常可读。通过为每个标志命名,可以毫不费力地阅读代码。

一个糟糕的方法是使用“魔术”数字:

if( _flags | 0x20 ) {  // What does this number mean?
   do_something();
}
于 2009-07-28T22:39:50.743 回答
1

是不是太简单了说:

flags ^= bit;
于 2009-08-30T17:21:03.977 回答
0

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

至于您的技术效率:-首先,摆脱 else 子句(如果它的值已经是 HIGH,为什么要将位设置为 HIGH ?)-其次,更喜欢首先有一些可读的东西,内联的 setter / getter 在内部使用位掩码会完成这项工作,高效且可读。

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

于 2009-07-29T12:47:23.437 回答
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(还),但可能已经有这类东西的宏,我不知道。

于 2009-07-28T22:44:48.690 回答