在C++中将字节数组解释为结构体的最佳方法

apeeds0o  于 2023-05-02  发布在  其他
关注(0)|答案(1)|浏览(109)

在现代C++中,解释字节串最有效、最优雅的方法是什么?我的第一次尝试是使用bit field。下面是一个例子,希望能解释任务的目的和难度:

union Data {
    uint8_t raw[2];
    struct __attribute__((packed)) {
        uint field1: 4, field2: 2, field3: 1, field4: 2;
        uint field5: 7;
    } interpreted;
};

int main() {
    static_assert(sizeof(Data) == 2);
    Data d{.raw{0x84, 0x01}};
    std::cout << d.interpreted.field1 << std::endl;
    std::cout << d.interpreted.field4 << std::endl;
    std::cout << d.interpreted.field5 << std::endl;
}

这种方法在计算上是有效的,但它不是可移植的,并且存储器中字段的顺序难以预测。
i386/gcc 11上的输出:

4
3
0

0x 84中的4最终在field 1中结束,而field 5使用0x 01中的最低有效位。有更好的办法吗?也许是一个牺牲一些处理效率以获得可维护性和可移植性的解决方案?

ctehm74n

ctehm74n1#

一个问题是联合类型双关是UB,尽管一些编译器可能允许它。另一个问题是比特字段的结构化方式不是UB而是实现定义的。也就是说,大多数编译器首先将低位部分的位字段打包,并允许跨越。它只是不能保证,但它应该由编译器规范定义。
一种安全有效的方法是使用一个单独的函数,该函数使用std::bit_cast返回一个Data对象,并在运行时执行一个测试,该测试检查实现并失败,可能是通过抛出异常。

#include <cstdint>
#include <iostream>
#include <bit>

// 0000000'11'0'00'0100 { 0x84, 0x01 };
struct Data {
    uint16_t field1 : 4, field2 : 2, field3 : 1, field4 : 2;
    uint16_t field5 : 7;
};

Data to_Data(uint8_t(&a)[2]) {
    return std::bit_cast<Data>(a);
}

// returns true if imnplimentation is OK
// fails to compile if size(Data)!=2
bool test_Data_implimentation()
{
    uint8_t a[2]{ 0x84, 0x01 };
    Data d = std::bit_cast<Data>(a);
    return d.field1 == 4 && d.field4 == 3 && d.field5 == 0;
}

int main() {
    if (test_Data_implimentation())
        std::cout << "Implementation passes\n";
    else
        std::cout << "Implementation fails\n";
    uint8_t a[2]{ 0x84, 0x01 };
    Data d = to_Data(a);
    std::cout << d.field1 << std::endl;
    std::cout << d.field4 << std::endl;
    std::cout << d.field5 << std::endl;
    //4
    //3
    //0
}

我还做了一个constexpr,自执行lambda,通过在编译时检查位字段是否被打包,不占用任何运行时代码,虽然非常常见,但它是实现定义的。除了编译时检查之外,它的优点是不向全局(或本地)名称空间添加任何内容。将其添加到任何已编译的函数将检查位字段实现和编译器的小端状态。实际上我这样做是因为它可以简化ICC(国际色彩联盟)配置文件结构的一些解码,这些配置文件结构被定义为二进制对象。

[]() {
    constexpr uint16_t i = 0b0000'0001'0000'1101;
    struct A {uint16_t i0 : 2, i1 : 3, i2 : 4, i3 : 7; };
    constexpr A a{ std::bit_cast<A>(i) };
    static_assert(a.i0 == 1 && a.i1 == 3 && a.i2 == 8 && a.i3 == 0);
}();

注意:Clang还没有为bit字段实现constexpr bit_cast。这是一个突出的bug。MSVC和GCC都有。对于那些使用MSVC的人来说,使用Clang的intelliense在一些代码中添加了红色的波浪线,但它仍然可以很好地与MSVC一起编译。

相关问题