繁体   English   中英

Boolean 位域与逻辑位掩码或位移 - C++

[英]Boolean bit fields vs logical bit masking or bit shifting - C++

我有一系列的课程需要 boolean 个字段,大约在 4-10 个之间。 我不想为每个 boolean 使用一个字节。我一直在研究位域结构,例如:

struct BooleanBitFields
    {
        bool b1:1;
        bool b2:1;
        bool b3:1;
        bool b4:1;
        bool b5:1;
        bool b6:1;
    };

但是在做了一些研究之后,我看到很多人说这会导致 memory 访问效率低下并且不值得节省 memory。 我想知道这种情况的最佳方法是什么。 我应该使用位字段,还是使用带有位掩码(and's and or s)的字符来存储 8 位? 如果第二种解决方案是位移位还是使用逻辑更好?

如果有人可以评论他们会使用什么方法以及为什么它会真正帮助我决定我应该下哪条路线 go。

提前致谢!

由于桌面盒上有大的地址空间,32/64位布尔值的阵列可能看起来很浪费,事实上确实如此,但大多数开发人员并不关心,(包括我)。 在RAM限制的嵌入式控制器上,或者在访问驱动程序中的硬件时,请确保使用位域,否则..

除了R / W简易/速度之外,另一个问题是32位或64位布尔值比中间的一位线程更安全,必须由多个逻辑运算来操作。

位字段仅是编译器的推荐。 编译器可以随意实现它们。 在嵌入式系统上,有一些编译器可以保证1位到位的映射。 其他编译器没有。

我会使用常规结构,就像你的结果,但没有位字段。 使它们成为无符号字符 - 最短的数据类型。 如果您的IDE支持自动完成,则结构将使编辑时更容易访问它们。

使用int位数组(留下很多空间来扩展,并且单个char没有优势)并使用掩码常量进行测试:

#define BOOL_A 1
#define BOOL_B 1 << 1
#define BOOL_C 1 << 2
#define BOOL_D 1 << 3

/* Alternately: use const ints for encapsulation */    

// declare and set
int bitray = 0 | BOOL_B | BOOL_D;

// test
if (bitray & BOOL_B) cout << "Set!\n";

我想写一个答案来再次确定并形式化这个想法:“从使用字节到使用位的转换需要什么?” 也因为“我不在乎”这个答案在我看来是不合理的。

探索 char 与位域

同意,这很诱人。 特别是当它应该像这样使用时:

#define FLAG_1 1
#define FLAG_2 (1 << 1)
#define FLAG_3 (1 << 2)
#define FLAG_4 (1 << 3)

struct S1 {
    char flag_1: 1;
    char flag_2: 1;
    char flag_3: 1;
    char flag_4: 1;
}; //sizeof == 1

void MyFunction(struct S1 *obj, char flags) {
    obj->flag_1 = flags & FLAG_1;
    obj->flag_2 = flags & FLAG_2;
    obj->flag_3 = flags & FLAG_3;
    obj->flag_4 = flags & FLAG_4;
    // we desire it to be as *obj = flags;
}

int main(int argc, char **argv)
{
    struct S1 obj;
    MyFunction(&obj, FLAG_1 | FLAG_2 | FLAG_3 | FLAG_4);
    
    return 0;
}

但让我们涵盖此类优化的所有方面。 让我们将操作分解为更简单的 C 命令,大致对应于汇编程序命令:

  1. 初始化所有标志。
    char flags = FLAG_1 | FLAG_3;
    //obj->flag_1 = flags & FLAG_1;
    //obj->flag_2 = flags & FLAG_2;
    //obj->flag_3 = flags & FLAG_3;
    //obj->flag_4 = flags & FLAG_4;
    *obj = flags;
  1. 写一个标志作为常量
    //obj.flag_3 = 1;
    char a = *obj;
    a &= ~FLAG_3;
    a |= FLAG_3;
    *obj = a;
  1. 使用变量写入单个标志
    char b = 3;
    //obj.flag_3 = b;
    char a = *obj;
    a &= ~FLAG_3;
    
    char c = b;
    c <<= 3;
    c &= ~FLAG_3; //Fixing b > 1
    
    a |= c;
    *obj = a;
  1. 将一个标志读入变量
    //char f = obj.flag_3;
    char f = *obj;
    f >>= 3;
    f &= 0x01;
  1. 将一个标志写入另一个标志
  //obj.flag_2 = obj.flag_4;
  char a = *obj;
  char b = a;
  a &= FLAG_4;
  a <<= 2; //Shift to FLAG_2 position
  b |= a;
  *obj = b;

恢复

命令 成本,位域 成本,可变
1.初始化 1个 4个或更少
2. obj.flag_3 = 1; 3个 1个
3. obj.flag_3 = b; 7 1 或 3 *
4. char f = obj.flag_3; 2个 1个
5. obj.flag_2 = obj.flag_4; 6个 1个

*- 如果我们保证标志不超过 1

除了初始化之外的所有操作都需要很多行代码。 看起来我们最好在初始化后单独保留位域))))。 但是,这通常是标志一直发生的情况。 他们在没有警告的情况下随机更改了 state。

我们本质上是在尝试通过牺牲频繁的值更改操作来降低稀有值初始化操作的成本。

在某些系统中,按位比较操作、位设置重置、位复制甚至位交换、位分支都需要一个周期。 甚至有些系统的互斥锁定操作是由单个汇编指令实现的(在这样的系统中,位字段可能不会位于整个 memory 区域,例如 PIC 单片机)。 无论如何,它都不是一个常见的 memory 区域。

也许在这样的系统中,bool 类型可以指向位域的一个组件。

如果您希望节省一个字节的微不足道的位还没有消失,请尝试考虑实现可寻址性、操作的原子性、字节算术以及由此产生的调用开销,数据memory,代码memory,如果算法放在堆栈中职能。

关于选择bool还是char的思考

如果您的目标平台将bool类型解码为 2 个字节或 4 个或更多字节。 最有可能使用位的操作不会被优化。 相反,它是一个用于大容量计算的平台。 这意味着对它的位操作要求不高,此外,对字节和字的操作要求也不高。

与对位的操作会损害性能一样,对单个字节的操作也会大大增加访问变量的周期数。

没有哪个系统可以同时对所有事情都同样优化。 与其沉迷于明显有大量 memory 盈余的系统中的 memory 节省,不如关注这些系统的优势。

结论

在以下情况下使用charbool

  1. 您需要存储可变的 state 或算法的行为(并单独更改和返回标志)。
  2. 您的标志没有准确描述系统,可能会演变成一个数字。
  3. 您需要能够通过地址访问标志。
  4. 如果您的代码声称与平台无关,并且不能保证位操作将在目标平台上得到优化。

在以下情况下使用位域:

  1. 您需要存储大量标志,而不必不断地读取和重写它们。
  2. 您对 memory 的要求异常严格,或者 memory 要求很低。
  3. 在其他非常合理的情况下,通过计算和确认实验。

也许一个简短的规则可能是:

独立标志存储在bool中。

PS:如果你已经读到这里并且仍然想保存 8 位中的 7 位,那么请考虑为什么不希望对最大取值为 100 的变量使用 7 位位域。

参考

Raymond Chen:布尔值集合的位域成本效益分析

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM