在编译时根据字节序定义位字段

Defining bitfields according to endianness at compile time

本文关键字:定义 字段 字节 编译      更新时间:2023-10-16

假设我有以下两个基于2个字节的结构:

#pragma pack(1)
struct Foo {
    unsigned short a : 5;
    unsigned short b : 4;
    unsigned short c : 2;
    unsigned short d : 5;
} ;
struct Bar {
    unsigned short a : 5;
    unsigned short b : 4;
    unsigned short c : 2;
    unsigned short d : 3;
    unsigned short e : 2;
} ;

我有一个包含他们的联盟:

union Baz {
    unsigned short val;
    struct Foo foo;
    struct Bar bar;
} ;

然后在我的程序中,我可以使用val设置一个值,并根据它们的位字段获得a、b、c、d和e值,无需按位操作/接口等。

然而,问题是我需要它同时支持大字节序和小字节序,这意味着我需要让我的结构在编译时根据字节序定义位字段。

因此,我需要这样的东西:

#pragma pack(1)
#if BIG_ENDIAN
struct Foo {
    unsigned short a : 5;
    unsigned short b : 4;
    unsigned short c : 2;
    unsigned short d : 5;
} ;
struct Bar {
    unsigned short a : 5;
    unsigned short b : 4;
    unsigned short c : 2;
    unsigned short d : 3;
    unsigned short e : 2;
} ;
#else
struct Foo {
    unsigned short d : 5;
    unsigned short c : 2;
    unsigned short b : 4;
    unsigned short a : 5;
} ;
struct Bar {
    unsigned short e : 2;
    unsigned short d : 3;
    unsigned short c : 2;
    unsigned short b : 4;
    unsigned short a : 5;
} ;
#endif

我试着查找它,我能找到的要么是运行时检查,要么是只能在运行时使用的编译值。我知道有很多宏,如BYTE_ORDER、LITTLEORDER、BIG_ORDER等,但我不能确保它们会在请求的部署环境中以及endian.h头文件中定义。此外,据我所知,boost的endian.hpp正在实现我上面所说的关于宏的内容,所以我不确定它会有什么不同。。

有什么建议吗?


第1版:在回复其中一条评论时:我需要一个c++03解决方案,但一个c++11/14解决方案对启蒙也很好。

对于一个简单的简短问题来说,这个问题太长了:"我如何在编译时知道endiannes。",遗憾的是,这个问题的答案是"你不能"。

问题是,无论是Posix还是C/C++标准,都没有指定任何关于endianness的内容。你唯一能做的就是测试已知的特定于体系结构的宏,并从中派生enddiannes

正如我在评论中所说,一个人可以用std::bitset煽动某事

然而,它有点冗长。

我还不能在big-endian机器上测试它,因此,如果有人可以使用它并发现一个bug,我将不胜感激

#include <iostream>
#include <iomanip>
#include <bitset>
#include <endian.h>
using std::size_t;
struct BE {};
struct LE {};
#if __BYTE_ORDER  ==  __BIG_ENDIAN
using PE = BE;
#else
using PE = LE;
#endif
template<typename> struct BitTraits{};
template<> struct BitTraits<uint8_t> {
    constexpr static size_t numbits = 8;
};
template<> struct BitTraits<uint16_t> {
    constexpr static size_t numbits = 16;
};
template<typename T>
class BitField {
public:
    using assignment_type = T;
    using container_type = 
        typename std::bitset<BitTraits<T>::numbits>;
    inline BitField(assignment_type data) : data_(data) {}
    inline BitField& operator=(assignment_type data) {
        data_ = data;
        return *this;
    }
    inline container_type& data() {
        return data_;
    } 
    inline unsigned long long to_ullong() const {
        return data_.to_ullong();
    }
private:    
    container_type data_;
};
template<size_t, typename Endianness = PE> struct BitSliceTraits;
template<size_t length> 
struct BitSliceTraits<
    length, 
    typename std::enable_if<1<=length && length<=8, PE>::type
> { 
    using assignment_type = uint8_t; 
    inline static assignment_type little_endian(assignment_type b) {
        return b;
    }
};
template<size_t length> 
struct BitSliceTraits<
    length, 
    typename std::enable_if<9<=length && length<=16, LE>::type
> { 
    using assignment_type = uint16_t; 
    inline static assignment_type little_endian(assignment_type b) {
        return b;
    }
};
template<size_t length> 
struct BitSliceTraits<
    length, 
    typename std::enable_if<9<=length && length<=16, BE>::type
> { 
    using assignment_type = uint16_t; 
    inline static assignment_type little_endian(assignment_type b) {
        assignment_type rv = (b & 0xFF) >> 8; 
        rv |= (b & 0xFF00) << 8;
        return rv;
    }
};

template<class B, size_t start, size_t end>
struct BitSlice {
    static constexpr size_t length = end - start + 1;
    using assignment_type = 
        typename BitSliceTraits<length>::assignment_type;
    using ref_type = typename B::container_type;
    inline explicit BitSlice(B& bf) noexcept : ref(bf.data())  {}
    inline BitSlice& operator= (assignment_type arg) noexcept {
        arg = BitSliceTraits<length>::little_endian(arg);
        for (size_t i = start; i <= end; ++i) {
            ref[i] = (arg & (1 << (i - start))) != 0 ? true : false;
        }
        return *this;
    }
    inline operator assignment_type () const noexcept {
        assignment_type rv(0);
        for (size_t i = start; i <= end; ++i) {
            rv |= (assignment_type(ref[i]) << (i - start));
        }
        return rv;
    }
    ref_type& ref;
}; 
template<typename B>
struct BitFieldView {
    using bitfield_type = B;
};
struct FooView : public BitFieldView<BitField<unsigned short>> {
    using bft = bitfield_type;
    inline explicit FooView (bft& bf) noexcept : a(bf), b(bf), c(bf) {}
    BitSlice<bft,  0, 11> a; // 12    
    BitSlice<bft, 12, 12> b; // 1
    BitSlice<bft, 13, 15> c; // 3
};
struct BarView : public BitFieldView<BitField<unsigned short>> {
    using bft = bitfield_type;
    inline explicit BarView (bft& bf) noexcept : a(bf), b(bf), c(bf), d(bf) {}
    BitSlice<bft,  0,  3> a; //4    
    BitSlice<bft,  4,  7> b; //4
    BitSlice<bft,  8, 11> c; //4
    BitSlice<bft, 12, 15> d; //4    
};

std::ostream& operator<<(std::ostream& os, uint8_t val) {
    return os << std::hex << ulong(val);
}

int main(){
    //   BIOS   FOOD
    //  0xB105 0xF00D 
    {
        BitField<unsigned short> bf = 0x0; 
        // 0xB105 = 1011 0001 0000 0101 = 101 1 000100000101;
        FooView(bf).a = 0b000100000101;
        FooView(bf).b = 1;
        FooView(bf).c = 0b101;
        std::cout << std::hex << bf.to_ullong() << ' '; 
    }
    {
                                        // 0xF00D
        BitField<unsigned short> bf = 0b1111000000001101;
        std::cout << BarView(bf).d 
                  << BarView(bf).c 
                  << BarView(bf).b 
                  << BarView(bf).a 
                  << std::endl; 
    }
    return EXIT_SUCCESS;
}

住在Coliru's

输出:

b105 f00d