繁体   English   中英

布尔值对位字段的优势

[英]Advantages of boolean values to bit-fields

我工作的代码库很旧。 虽然我们使用c ++ 11编译几乎所有内容。 许多代码是多年前用c编写的。 在旧的地区开发新课程时,我总是发现自己不得不选择匹配的旧方法,还是采用更现代的方法。

在大多数情况下,我宁愿尽可能使用更现代的技术。 但是,我经常看到的一种常见的老方法是位域,我很难争论使用它。 我们传递了很多消息,在这里很多次,它们充满了单个位的值。 请看下面的例子:

class NewStructure
{
public:

    const bool getValue1() const
    {
        return value1;
    }

    void setValue1(const bool input)
    {
        value1 = input;
    }

private:
    bool value1;
    bool value2;
    bool value3;
    bool value4;
    bool value5;
    bool value6;
    bool value7;
    bool value8;
};

struct OldStructure
{
    const bool getValue1() const
    {
        return value1;
    }

    void setValue1(const bool input)
    {
        value1 = input;
    }

    unsigned char value1 : 1;
    unsigned char value2 : 1;
    unsigned char value3 : 1;
    unsigned char value4 : 1;
    unsigned char value5 : 1;
    unsigned char value6 : 1;
    unsigned char value7 : 1;
    unsigned char value8 : 1;
};

在这种情况下,新结构的大小为8个字节,旧结构的大小为1个字节。
我添加了“ getter”和“ setter”来说明从用户角度来看,它们可以相同。 我意识到,也许您可​​以为下一个开发人员提供可读性,但是除此之外,还有理由避免位字段吗? 我知道压缩字段会影响性能,但是由于这些都是字符,因此填充规则仍然存在。

使用位域时需要考虑几件事。 这些是(重要性顺序取决于情况)

  • 性能

设置或读取时,位域操作会导致性能下降(与直接类型相比)。 一个简单的代码生成示例显示了发出的额外指令: https : //gcc.godbolt.org/z/DpcErN但是,位域提供了更紧凑的数据,这变得对缓存更友好,并且可以完全弥补附加操作的任何弊端。 理解实际性能影响的唯一方法是在实际用例中对实际应用进行基准测试。

  • ABI互操作性

位字段的字节序是实现定义的,因此两个编译器生成的相同结构的布局可以不同。

  • 易用性

没有引用绑定到位域,也不能使用它的地址。 这可能会影响代码并使其不清楚。

对于您来说,作为程序员,没有太大的区别。 但是,与访问单个位相比,访问整个字节的机器代码要简单/短得多,因此使用位域会堆积所生成的代码。

用伪汇编语言,您的设置方法可能会变成类似以下内容:

    ldb input1,b         ; get the new value into accumulator b
    movb b,value1        ; put it into the variable
    rts                  ; return from subroutine

但这对于位域而言并不容易:

    ldb input1,b        ; get the new value into accumulator b
    movb bitfields,a    ; get current bitfield values into accumulator a
    cmpb b,#0           ; See what to do.
    brz clearvalue1:    ; If it's zero, go to clearing the bit
    orb #$80,a          ; set the bit representing value1.
    bra resume:         ; skip the clearing code.
clearvalue1:
    andb #$7f,a         ; clear the bit representing value1
resume:
    movb a,bitfields    ; put the value back
    rts                 ; return

它必须为您的8个成员的设置者执行此操作,对于getter则必须执行类似操作。 它加起来。 此外,即使是当今最笨拙的编译器,也可能会内联完整字节的setter代码,而不是实际进行子例程调用。 对于位域设置器,这可能取决于您是否要针对速度与空间进行编译优化。

而且您只问过布尔值。 如果它们是整数位字段,则编译器必须处理加载,屏蔽先前的值,将值移动以使其对齐到其字段中,屏蔽未使用的位and / or将值放在适当的位置,然后将其写回到内存中。

那么,为什么要使用一个与另一个?

  • 位域速度较慢,但​​打包数据效率更高。
  • 非位域速度更快,并且需要更少的机器代码来访问。

作为开发人员,这是您的判断力。 如果您要一次在内存中保留许多Structure实例,那么节省内存可能是值得的。 如果您不会一次在内存中拥有该结构的多个实例,那么编译后的代码膨胀会抵消内存的节省,并且您正在牺牲速度。

template<typename enum_type,size_t n_bits>
class bit_flags{
    std::bitset<n_bits> bits;
    auto operator[](enum_type bit){return bits[bit];};
    auto& set(enum_type bit)){return set(bit);};
    auto& reset(enum_type bit)){return set(bit);};
     //go on with flip et al...
static_assert(std::is_enum<enum_type>{});
 };

enum class  v_flags{v1,v2,/*...*/vN};

bit_flags<v_flags,v_flags::vN+1> my_flags;

my_flags.set(v_flags::v1);
my_flags.[v_flags::v2]=true;

std::bitsetbool位字段一样有效。 您可以将其包装在一个类中,以强制使用enum定义的名称的每一位。 现在,您有了一个小型但可扩展的实用程序,可用于多个不同的bool标志集。 C ++ 17使它更加方便:

template<auto last_flag, typename enum_type=decltype(last_flag)>
class bit_flags{
    std::bitset<last_flag+1> bits;
    //...
};

bit_flags<v_flags::vN+1> my_flags;

暂无
暂无

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

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