简体   繁体   English

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

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

I have a series of classes that are going to require many boolean fields, somewhere between 4-10.我有一系列的课程需要 boolean 个字段,大约在 4-10 个之间。 I'd like to not have to use a byte for each boolean. I've been looking into bit field structs, something like:我不想为每个 boolean 使用一个字节。我一直在研究位域结构,例如:

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

But after doing some research I see a lot of people saying that this can cause inefficient memory access and not be worth the memory savings.但是在做了一些研究之后,我看到很多人说这会导致 memory 访问效率低下并且不值得节省 memory。 I'm wondering what the best method for this situation is.我想知道这种情况的最佳方法是什么。 Should I use bit fields, or use a char with bit masking (and's and or s) to store 8bits?我应该使用位字段,还是使用带有位掩码(and's and or s)的字符来存储 8 位? If the second solution is it better to bit shift or use logic?如果第二种解决方案是位移位还是使用逻辑更好?

If anyone could comment as to what method they would use and why it would really help me decide which route I should go down.如果有人可以评论他们会使用什么方法以及为什么它会真正帮助我决定我应该下哪条路线 go。

Thanks in advance!提前致谢!

With the large address spaces on desktop boxes, an array of 32/64-bit booleans may seem wasteful, and indeed it is, but most developers don't care, (me included). 由于桌面盒上有大的地址空间,32/64位布尔值的阵列可能看起来很浪费,事实上确实如此,但大多数开发人员并不关心,(包括我)。 On RAM-restricted embedded controllers, or when accessing hardware in drivers, then sure, use bitfields, otherwise.. 在RAM限制的嵌入式控制器上,或者在访问驱动程序中的硬件时,请确保使用位域,否则..

One other issue, apart from R/W ease/speed, is that a 32- or 64-bit boolean is thread-safer than one bit in the middle that has to be manipulated by multiple logical operations. 除了R / W简易/速度之外,另一个问题是32位或64位布尔值比中间的一位线程更安全,必须由多个逻辑运算来操作。

Bit fields are only a recommendation for the compiler. 位字段仅是编译器的推荐。 The compiler is free to implement them as it likes. 编译器可以随意实现它们。 On embedded systems there are compilers that guarantee 1 bit-to-bit mapping. 在嵌入式系统上,有一些编译器可以保证1位到位的映射。 Other compilers don't. 其他编译器没有。

I would go with a regular struct, like yours but no bit fields. 我会使用常规结构,就像你的结果,但没有位字段。 Make them unsigned chars - the shortest data type. 使它们成为无符号字符 - 最短的数据类型。 The struct will make it easier to access them while editing, if your IDE supports auto completion. 如果您的IDE支持自动完成,则结构将使编辑时更容易访问它们。

Use an int bit array (leaves you lots of space to expand, and there is no advantage to a single char) and test with mask constants: 使用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";

I want to write an answer to make sure once again and formalize the thought: "What does the transition from working with bytes to working with bits entail?"我想写一个答案来再次确定并形式化这个想法:“从使用字节到使用位的转换需要什么?” And also because the answer "I don't care" seems to me to be unreasonable.也因为“我不在乎”这个答案在我看来是不合理的。

Exploring char vs bitfield探索 char 与位域

Agree, It's very tempting.同意,这很诱人。 Especially when it's supposed to be used like this:特别是当它应该像这样使用时:

#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;
}

But let's cover all aspects of such optimization.但让我们涵盖此类优化的所有方面。 Let's decompose the operation into simpler C-commands, roughly corresponding to the assembler commands:让我们将操作分解为更简单的 C 命令,大致对应于汇编程序命令:

  1. Initialization of all flags.初始化所有标志。
    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. Writing one flag as a constant写一个标志作为常量
    //obj.flag_3 = 1;
    char a = *obj;
    a &= ~FLAG_3;
    a |= FLAG_3;
    *obj = a;
  1. Write a single flag using a variable使用变量写入单个标志
    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. Reading one flag into variable将一个标志读入变量
    //char f = obj.flag_3;
    char f = *obj;
    f >>= 3;
    f &= 0x01;
  1. Write one flag to another将一个标志写入另一个标志
  //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;

Resume恢复

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

*- if we guarantee flag be no more than 1 *- 如果我们保证标志不超过 1

All operations except initialization take many lines of code.除了初始化之外的所有操作都需要很多行代码。 It looks like it would be better for us to leave bit fields alone after initialization)))).看起来我们最好在初始化后单独保留位域))))。 However, this is usually what happens to flags all the time.但是,这通常是标志一直发生的情况。 They change their state without warning and randomly.他们在没有警告的情况下随机更改了 state。

We are essentially trying to make the rare value initialization operation cheaper by sacrificing frequent value change operations.我们本质上是在尝试通过牺牲频繁的值更改操作来降低稀有值初始化操作的成本。

There are systems in which bitwise comparison operations, bit set and reset , bit copying and even bit swapping , bit branching , take one cycle.在某些系统中,按位比较操作、位设置重置、位复制甚至位交换、位分支都需要一个周期。 There are even systems in which mutex locking operations are implemented by a single assembler instruction (in such systems, bit fields may not be located on the entire memory area, for example, PIC microcontrollers).甚至有些系统的互斥锁定操作是由单个汇编指令实现的(在这样的系统中,位字段可能不会位于整个 memory 区域,例如 PIC 单片机)。 in any way it's not a common memory area.无论如何,它都不是一个常见的 memory 区域。

Perhaps in such systems, the bool type could point to a component of the bitfield.也许在这样的系统中,bool 类型可以指向位域的一个组件。

If your desire to save on insignificant bits of a byte has not yet disappeared, try to think about implementing addressability , atomicity of operations, arithmetic with bytes, and the resulting overhead for calls , data memory, code memory, stack if algorithms are placed in functions.如果您希望节省一个字节的微不足道的位还没有消失,请尝试考虑实现可寻址性、操作的原子性、字节算术以及由此产生的调用开销,数据memory,代码memory,如果算法放在堆栈中职能。

Reflections on the choice of bool or char关于选择bool还是char的思考

If your target platform decodes the bool type as 2 bytes or 4 or more.如果您的目标平台将bool类型解码为 2 个字节或 4 个或更多字节。 That most likely operations with bits on it will not be optimized.最有可能使用位的操作不会被优化。 Rather, it is a platform for high-volume computing.相反,它是一个用于大容量计算的平台。 This means that bit operations are not so in demand on it, in addition, operations with bytes and words are not so in demand on it.这意味着对它的位操作要求不高,此外,对字节和字的操作要求也不高。

In the same way that operations on bits hurt performance, operations on a single byte can also greatly increase the number of cycles to access a variable.与对位的操作会损害性能一样,对单个字节的操作也会大大增加访问变量的周期数。

No system can be equally optimal for everything at once.没有哪个系统可以同时对所有事情都同样优化。 Instead of obsessing over memory savings in systems that are clearly built with a lot of memory surplus, pay attention to the strengths of those systems.与其沉迷于明显有大量 memory 盈余的系统中的 memory 节省,不如关注这些系统的优势。

Conclusion结论

Use char or bool if:在以下情况下使用charbool

  1. You need to store the mutable state or behavior of the algorithm (and change and return flags individually).您需要存储可变的 state 或算法的行为(并单独更改和返回标志)。
  2. Your flag does not accurately describe the system and could evolve into a number.您的标志没有准确描述系统,可能会演变成一个数字。
  3. You need to be able to access the flag by address.您需要能够通过地址访问标志。
  4. If your code claims to be platform independent and there is no guarantee that bit operations will be optimized on the target platform.如果您的代码声称与平台无关,并且不能保证位操作将在目标平台上得到优化。

Use bitfields if:在以下情况下使用位域:

  1. You need to store a huge number of flags without having to constantly read and rewrite them.您需要存储大量标志,而不必不断地读取和重写它们。
  2. You have unusually tight memory requirements, or memory is low.您对 memory 的要求异常严格,或者 memory 要求很低。
  3. In other deeply justified cases, with calculations and confirming experiments.在其他非常合理的情况下,通过计算和确认实验。

Perhaps a short rule might be:也许一个简短的规则可能是:

Independent flags are stored in a bool .独立标志存储在bool中。

PS: If you've read this far and still want to save 7 bits out of 8, then consider why there is no desire to use 7 bit bit fields for variables that take a value up to 100 maximum. PS:如果你已经读到这里并且仍然想保存 8 位中的 7 位,那么请考虑为什么不希望对最大取值为 100 的变量使用 7 位位域。

References参考

Raymond Chen: The cost-benefit analysis of bitfields for a collection of booleans Raymond Chen:布尔值集合的位域成本效益分析

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

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