如何从字节数组获取任意非默认构造类型T的合法实例且无未定义行为?
如何从std::byte*指向的字节数组中安全获取任意类型T的对象(无未定义行为)
我正在处理Rust与C的FFI交互,已知某个std::byte*指针指向的字节数组是一个合法的C类型T的对象(T可以是任意类型)。现在需要从这个字节数组中获取合法的T类型值,但以下几种方法都不可行:
不可行方法1:reinterpret_cast违反严格别名规则触发UB
直接通过reinterpret_cast转换指针后解引用,会违反C++的严格别名规则,导致未定义行为:
template<typename T> T transmute(std::byte *ptr) { return *reinterpret_cast<T *>(ptr); // UB }
不可行方法2:memcpy依赖T的默认构造能力
这种方法虽被不少资料推荐,但无法适用于没有默认构造函数的T:
template<typename T> T transmute(std::byte *ptr) { T value; // 要求T具备默认构造函数 memcpy(&value, ptr, sizeof(T)); return value; }
不可行方法3:联合体读取非活跃成员导致UB
C++标准明确规定,读取联合体的非活跃成员属于未定义行为,因此该方法不可行:
template<typename T> T transmute(std::byte *ptr) { union { T value; std::byte bytes[sizeof(T)]; } thing; memcpy(&thing.bytes, ptr, sizeof(T)); return thing.value; // UB }
可行解决方案
C++17及以上:结合std::launder与移动/拷贝构造
利用std::launder告知编译器指针指向的内存中已存在合法的T对象,绕过严格别名规则,同时不需要T具备默认构造能力:
#include <new> #include <type_traits> template<typename T> T transmute(std::byte* ptr) { // 前置检查:T需可移动或可拷贝(FFI场景下这类类型才具备传递意义) static_assert(std::is_move_constructible_v<T> || std::is_copy_constructible_v<T>, "T must be move or copy constructible to be returned"); // 用std::launder确保编译器识别内存中的T对象 T& obj = *std::launder(reinterpret_cast<T*>(ptr)); // 移动构造返回值(避免不必要的拷贝,若T不可移动则自动退化为拷贝) return std::move(obj); }
原理:std::launder的作用是打破编译器对指针指向类型的原有假设,明确告知内存中存在一个合法的T实例,从而避免严格别名规则导致的未定义行为。
C++20及以上:使用std::bit_cast(更简洁)
C++20引入的std::bit_cast支持类型间的逐位转换,无需默认构造函数,只需T是可平凡复制类型(FFI场景下大多满足):
#include <bit> #include <array> #include <type_traits> template<typename T> requires std::is_trivially_copyable_v<T> T transmute(std::byte* ptr) { static_assert(sizeof(T) == sizeof(std::array<std::byte, sizeof(T)>), "Size mismatch (should never happen)"); // 将字节数组转换为std::array后,通过bit_cast得到T类型值 return std::bit_cast<T>(*reinterpret_cast<std::array<std::byte, sizeof(T)>*>(ptr)); }
注意:若T不是可平凡复制类型,需使用C++17的方案。
内容的提问来源于stack exchange,提问作者Nicola Gigante
相关产品推荐
相关产品推荐

