【问题标题】:Advantages of boolean values to bit-fields布尔值对位域的优势
【发布时间】:2019-03-20 13:48:35
【问题描述】:

我工作的代码库已经很老了。虽然我们用 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”来说明从用户的角度来看,它们可以是相同的。我意识到也许您可以为下一个开发人员提供可读性的案例,但除此之外,是否有理由避免使用位字段?我知道压缩字段会影响性能,但因为这些都是字符,所以填充规则仍然存在。

【问题讨论】:

  • 当都是单个位时,我看不出使用位域的原因。我可能会选择std::bitset(或它的邪恶兄弟std::vector<bool>;)

标签: c++ boolean bit-fields


【解决方案1】:

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

  • 性能

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

  • ABI 互操作性

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

  • 可用性

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

【讨论】:

  • +1 对于您的所有观点,但仅供参考,如果性能严格取决于指令数量(我知道您通常不能这样做),它们实际上是相同的。我粘贴在我的代码中,一次注释掉一个结构并添加一个打印语句,在这两种情况下都得到了 28 行指令。
【解决方案2】:

对于作为程序员的你来说,差别不大。但是访问整个字节的机器代码比访问单个位更简单/更短,因此使用位域会增加生成的代码。

在伪汇编语言中,你的 setter 可能会变成这样的东西:

    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 个成员的 setter 中的每一个都执行此操作,对于 getter 也必须这样做。它加起来。此外,即使是当今最愚蠢的编译器也可能会内联全字节设置器代码,而不是实际进行子例程调用。对于位域设置器,它可能取决于您是否正在编译优化速度与空间。

而且您只询问了布尔值。如果它们是整数位域,那么编译器必须处理加载、屏蔽先前的值、将值移位以对齐到其字段、屏蔽未使用的位、and/or 值到位,然后将其写回记忆。

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

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

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

【讨论】:

    【解决方案3】:
    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;
    

    【讨论】:

      猜你喜欢
      • 2014-10-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多