24 位和 8 位变量联合的定义行为

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

我正在尝试找到将 24 位和 8 位无符号整数打包为 32 位的最佳方法,而不需要位移来提取数据。工会立即想到了一个简单的方法,如下所示:

union {
    uint32_t u24;
    uint8_t u8[4]; // use only u8[3]
}

但是,这种方法会导致基于系统字节序的未定义行为,因此我提出了以下方法,该方法使用 c++20 功能在编译时使用 std::endian 和 constexpr 检测系统字节序:

#include <bit>
struct UnionTest {
    union {
        uint32_t u24;
        uint8_t u8[4];
    };
    
    inline constexpr uint8_t get_u8_index() const noexcept {
        if constexpr (std::endian::native == std::endian::little) return 0;
        else if constexpr (std::endian::native == std::endian::big) return 3;
        else // crap the bed
    }
};

// use like this:
int main() {
    UnionTest test;
    test.u24 = 0xffffff;
    test.u8[test.get_u8_index()] = 0xff;
}

这可能仍然有点冗长,但这不是问题。我纯粹对这种方法的可行性感兴趣,假设我们从不将大于 24 位的值写入 u24。

另一种方法是使用位字段:

struct UnionTest {
    uint32_t u24 : 24;
    uint32_t u8 : 8;
}

但这可能会导致 64 位而不是 32 位(尽管在大多数情况下应该是 32 位)。

我的问题是 A) 关于联合方法在性能和潜在未定义行为方面的可行性,以及 B) 提议的联合方法和 C++ 位字段的使用之间的实际差异

c++ unions bit-fields
1个回答
0
投票

C++ 语言允许访问任何对象上的字节表示。它明确用于允许普通可复制类型的字节复制。此外,如果定义了字节序,则可以预期 24 位值使用 3 个高位字节表示小端字节序,使用 3 个低位字节表示大端字节序。访问 24 位值仍然需要一个掩码,但可以直接访问 8 位值,并且不使用任何移位。

这是一个可能的代码,证明了这一点:

#include <iostream>
#include <bit>

namespace {
    inline constexpr uint8_t get_u8_index() noexcept {
        if constexpr (std::endian::native == std::endian::little) return 3;
        else if constexpr (std::endian::native == std::endian::big) return 0;
        else {}// crap the bed
    }
}

class pack_24_8 {
    uint32_t value;

    static const int u8_index = get_u8_index();  // locally scoped constant

public:
    uint8_t get_u8() const {
        return ((const uint8_t*)(&value))[u8_index]; // extract one single byte
    }

    void set_u8(uint8_t c) {
        ((uint8_t*)(&value))[u8_index] = c;  // set one single byte
    }

    uint32_t get_u24() const {
        return value & 0xffffff;      // get the less significant 24 bits
    }

    void set_u24(uint32_t u24) {
        uint8_t u8 = get_u8();    // save the u8 part
        value = u24;
        set_u8(u8);               // and restore it
    }
};

// use like this:
int main() {
    pack_24_8 test;
    test.set_u8(0x5a);
    test.set_u24(0xa5a5a5);

    std::cout << std::hex << (unsigned int) test.get_u8() << " - " <<
        std::hex << test.get_u24() << '\n';

    return 0;
}
© www.soinside.com 2019 - 2024. All rights reserved.