将一个结构映射到 uint64_t

4
什么是将下面的结构转换为uint64_t的正确方法?
struct Data
{
    uint64_t sign : 1;
    uint64_t exp : 4;
    uint64_t man : 8;
};

static_assert(sizeof(Data) == sizeof(uint64_t));

显而易见的是

Data data;
uint64_t n = *(reinterpret_cast<const uint64_t*>(&data));

但它不会被编译为 constexpr 并在 GCC 中产生以下警告:

dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]

编辑1:

uint64_t 的结果值可能因不同编译器而异。但当我从 uint64_t 转换回数据结构时,数据结构的值应该是相同的。

因此,更准确地说,我需要:

Data data;
uint64_t n = convert(data);
Data data2 = convert_back(n);
static_assert(data == data2);

1+4+8 --> 13。Dmitriano,“static_assert(sizeof(Data) == sizeof(uint64_t));”是错误的,因为“sizeof(Data)”可能是2。 - chux - Reinstate Monica
@chux-ReinstateMonica 更改了 int -> uint。这是一个笔误。 - Dmitriano
sizeof(Data) 不可能为2,必须为8,因为底层类型为64位,只赋值了13位含义。 - Remy Lebeau
@RemyLebeaum 嗯 - chux - Reinstate Monica
3个回答

5

这种转换不是"显而易见"的,因为Data的确切布局是由实现定义的(请参见位域)。此外,您通过指针进行的强制转换违反了严格别名规则,正如错误所示。在data的地址上没有存储uint64_t

您可以按照以下方式进行转换:

constexpr uint64_t Data_2_uint(const Data& d){
    return d.sign + (d.exp << 1) + (d.man << 5);
}

是的,看起来 sizeof(Data) 也是实现定义的。 - Dmitriano

5

你可以使用 std::memcpy 将该结构体复制到一个 uint64_t 上。由于该结构体是平凡可复制的,所以使用 std::memcpy 是一种合法的类型转换方式。


C++20 中还有 std::bit_cast

std::memcpy 不同的是,它是 constexpr 的,但为了使其在编译时起作用,我必须在结构体末尾添加 uint64_t : 51;

有趣的是,Clang(与GCC和MSVC不同)在编译时拒绝执行它

note: constexpr bit_cast involving bit-field is not yet supported

std::memcpy 需要使用 reinterpret_cast,对吗? - Dmitriano
@Dmitriano 不可以,因为它接收的是 void *。但是将其转换为 char * 是合法的,因为 char 指针不受严格别名规则的限制。 - HolyBlackCat
std::memcpy is not constexpr - Dmitriano
@Dmitriano 是的。但是一个正常的编译器可以优化它并/或在编译时执行它。 - HolyBlackCat
1
@Dmitriano 如果你想要constexpr,可以使用std::bitcast。请参见编辑。 - HolyBlackCat

1
如果您正在寻找隐式转换,可以使用union以不同的别名访问相同的数据结构。
union Data {
    struct {
        uint64_t sign : 1;
        uint64_t exp : 4;
        uint64_t man : 8;
    }
    uint64_t data_as_uint64;
}

为了确保字段填充不会导致内存表示问题,您应该使用#pragma pack,并将填充设置为1。
#pragma pack(push, 1)
union Data {
    struct {
        uint64_t sign : 1;
        uint64_t exp : 4;
        uint64_t man : 8;
    }
    uint64_t data_as_uint64;
}
#pragma pack(pop)

请注意,#pragma pack是与编译器相关的,您需要确保您的编译器支持它。

6
有些编译器允许使用union进行类型转换,但这其实是一种扩展功能。从技术上讲,访问一个union中未被激活的成员是未定义行为。 - 463035818_is_not_a_number
2
#pragma pack 看起来像是 C++ 编译器的扩展,而不是 C++ 的一部分。 - chux - Reinstate Monica
@chux-ReinstateMonica 您是正确的,但大多数流行的编译器都支持它 - msvcgccclang - user107511

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接