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

问题描述 投票:0回答:1

在现代 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/gcc11 上的输出:

4
3
0

来自 0x84 的 4 在 field1 中结束,而 field5 使用 0x01 中的最低有效位。有没有更好的办法?也许是为了可维护性和可移植性而牺牲一些处理效率的解决方案?

c++ struct union raw-data
1个回答
0
投票

一个问题是联合类型双关是 UB,尽管一些编译器可能允许它。另一个问题是位字段的结构方式不是 UB,而是实现定义的。也就是说,大多数编译器首先在低位部分打包位域并允许跨越。它只是不能保证,但它应该由编译器规范定义。

安全有效地做到这一点的一种方法是使用一个单独的函数,该函数使用

Data
返回一个
std::bit_cast
对象,以及一个最初在运行时执行的测试,该测试检查实现并失败,可能是通过抛出异常。

#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
}
© www.soinside.com 2019 - 2024. All rights reserved.