题
我有一个 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
如果您删除需要使用常量HIGH
和LOW
,通过分成两种方法,可以更清晰。只是要bitSet
和bitClear
方法。 bitSet
设置位HIGH
,并bitClear
设置位LOW
。然后,它变为:
#define SHIFT_BIT 0
if (bitRead(_flags, SHIFT_BIT) == HIGH)
{
bitClear(_flags, SHIFT_BIT);
}
else
{
bitSet(_flags, SHIFT_BIT);
}
当然,如果你只是有HIGH == 1
和LOW == 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;