在 C++20 中将 uint64_t 类型双关为两个 uint32_t

Seb*_*ann 7 c++ type-punning c++20

由于严格的别名规则,将 a 读uint64_t为 2 的代码是 UB:uint32_t

uint64_t v;
uint32_t lower = reinterpret_cast<uint32_t*>(&v)[0];
uint32_t upper = reinterpret_cast<uint32_t*>(&v)[1];
Run Code Online (Sandbox Code Playgroud)

uint64_t同样,由于同样的原因,编写 an 的上下部分的代码是 UB:

uint64_t v;
uint32_t* lower = reinterpret_cast<uint32_t*>(&v);
uint32_t* upper = reinterpret_cast<uint32_t*>(&v) + 1;

*lower = 1;
*upper = 1;
Run Code Online (Sandbox Code Playgroud)

如何在现代 C++20 中以一种安全、干净的方式编写这段代码,并可能使用std::bit_cast

Art*_*oul 10

使用std::bit_cast

在线尝试一下!

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

int main() {
    uint64_t x = 0x12345678'87654321ULL;
    // Convert one u64 -> two u32
    auto v = std::bit_cast<std::array<uint32_t, 2>>(x);
    std::cout << std::hex << v[0] << " " << v[1] << std::endl;
    // Convert two u32 -> one u64
    auto y = std::bit_cast<uint64_t>(v);
    std::cout << std::hex << y << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

输出:

87654321 12345678
1234567887654321
Run Code Online (Sandbox Code Playgroud)

std::bit_cast仅在 C++20 中可用。在 C++20 之前,您可以std::bit_cast通过std::memcpy手动实现,但有一个例外,这种实现与constexprC++20 变体不同:

template <class To, class From>
inline To bit_cast(From const & src) noexcept {
    //return std::bit_cast<To>(src);
    static_assert(std::is_trivially_constructible_v<To>,
        "Destination type should be trivially constructible");
    To dst;
    std::memcpy(&dst, &src, sizeof(To));
    return dst;
}
Run Code Online (Sandbox Code Playgroud)

对于整数的这种特定情况,最理想的方法就是进行位移/或算术,将一个 u64 转换为两个 u32,然后再转换回来。std::bit_cast更通用,支持任何可简单构造的类型,尽管 std::bit_cast 解决方案应该与具有高水平优化的现代编译器上的位算术相同。

位算术的一个额外好处是它可以正确处理字节顺序,它与字节顺序无关,与std::bit_cast.

在线尝试一下!

#include <cstdint>
#include <iostream>

int main() {
    uint64_t x = 0x12345678'87654321ULL;
    // Convert one u64 -> two u32
    uint32_t lo = uint32_t(x), hi = uint32_t(x >> 32);
    std::cout << std::hex << lo << " " << hi << std::endl;
    // Convert two u32 -> one u64
    uint64_t y = (uint64_t(hi) << 32) | lo;
    std::cout << std::hex << y << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

输出:

87654321 12345678
123456788765432
Run Code Online (Sandbox Code Playgroud)

注意!正如@Jarod42指出的,具有位移位的解决方案并不等同于memcpy/bit_cast解决方案,它们的等价性取决于字节顺序。在小端 CPU 上memcpy/bit_cast给出最低有效一半 (lo) 作为数组元素v[0]和最高有效 (hi) 在 中v[1],而在大端 CPU 上最低有效 (lo) 为v[1],最高有效为v[0]。虽然位移解决方案与字节序无关,并且在所有系统上给出最高有效一半 (hi) 作为uint32_t(num_64 >> 32)和最低有效一半 (lo) 作为uint32_t(num_64)

  • 请注意,手动位移的解决方案并不等同于具有字节顺序的“memcpy”/“bitcast”。 (2认同)