我有一个 Arduino 应用程序(实际上是一个库),其中有许多状态标志 - 最初我只是将它们声明为 int(在本例中是 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))
有帮助吗?

解决方案

请查看 Raymond Chen的优秀采取上这个问题。总之,你需要做一些详细的计算,找出后者的情况下是否真的更有效,这取决于有多少个对象有与多少callsites实际上设置这些状态。

至于可读性,它看起来像你和成员变量这样做,这意味着你可能已经封装他们不错的功能。在这种情况下,我并不关心与可读性,因为至少使用了类人的代码看起来不错。但是,你总是可以将其封装在专用的功能,如果它是一个问题。

其他提示

根据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 < - 返回单个位标志或通过一级获得标志的整个INT价值:variable.val < - 返回int

如果您删除需要使用常量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 == 1LOW == 0,那么你就不需要==检查。

要我的眼睛,甚至你的代码,后者仍然是相当可读。通过给一个名称,你的标志中的每一个,代码可以不费力地阅读。

一个好办法做到这一点是使用“魔术”号:

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

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

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

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

  • 第一个版本更好。并发。在第二个中,您有读取-修改-写入,因此您需要确保内存字节不会同时访问。通常您会禁用中断,这会在一定程度上增加中断延迟。另外,忘记禁用中断可能会导致非常令人讨厌且难以发现的错误(迄今为止我遇到的最令人讨厌的错误正是这种)。

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

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

  • 如果您想一次测试多个位(例如是设置的位之一)。

如果你谈论的可读性,位组和C ++,我为什么不找在那里std::bitset什么?据我所知,嵌入式编程竞赛是位掩码相当舒适,并已演变为它的纯粹uglyness失明(口罩,而不是种族:),但除了口罩与位域,标准库有一个很优雅的解决方案了。

的示例:

#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.

有关位字段,它更好地利用逻辑运算,所以你可以这样做:

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(还)播放,但有可能已经是宏这样的事情,我不知道。

我会说我关心的第一件事是: “#定义SHIFT 0” 为什么不利用恒定的,而不是宏?至于效率得到,该恒定允许确定的类型,从而确保比没有转换将在后面需要。

至于你TECHNIC的效率: - 第一,摆脱了else子句(为什么设置有点高,如果它的价值已经很高了?) - 第二,希望有东西可读第一,内联设置器/使用位内部掩蔽会做的工作的吸气剂,是有效的并且是可读

作为用于存储,用于C ++我会倾向于使用一个bitset(具有枚举相结合)。

时,它也只是简单地说:

flags ^= bit;
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top