以下代码是经典的,臭名昭着的类型 - 惩罚联盟。类似的代码在C99 / C11中是合法的,但在C ++中是非法的,因为必须从非活动成员中读取。
要激活非活动成员,必须写入(此处不合适)或通过placement-new构建它。
下面的代码就是这样:它初始化成员a
,然后通过placement-new激活成员b
。在原始类型数组的情况下,构造实际上是noop,并且bitvalues保持不变。
我从未在这个topix的众多其他问题/答案中看到这个解决方案,所以我想知道下面的代码是否真的绕过了UB。
#include <cstdint>
#include <cstddef>
#include <utility>
#include <array>
#include <tuple>
#include <cassert>
union ByteUnion32 {
public:
explicit ByteUnion32(uint32_t v) : a{v}{
new (&b) std::byte[sizeof(uint32_t)]; // set b active, but prevents constexpr, actually a noop
}
std::byte operator[](uint8_t i) const {
return b[i];
}
private:
uint32_t a;
std::byte b[sizeof(uint32_t)];
};
int main(){
ByteUnion32 u10{0x01020304}; // initialise and activate byte-memebers
auto v10 = std::to_integer<uint8_t>(u10[0]);
assert(v10 == 4);
}
答案 0 :(得分:1)
如果没有为对象指定初始值设定项,则默认初始化该对象。当获得具有自动或动态存储持续时间的对象的存储时,该对象具有不确定值,并且如果没有对该对象执行初始化,则该对象保留不确定的值,直到该值被替换为止( [expr.ass])。 [...]如果评估产生了不确定的值,则行为是不确定的,除非在下列情况下:[...]
在此代码中:
assert(v10 == 4);
v10
本身的初始化是未定义的行为,因为不适合任何项目符号(我不完全确定这一点),或者v10
的初始化很好而且它只是被认为具有不确定的值,此时与4
的最终比较是未定义的行为。
无论哪种方式,这都是未定义的行为。
答案 1 :(得分:0)
以下可能是C ++中使用两个特化的转换方向std::byte
到更大的整数类型或整数类型到std::byte
的解决方案,而不是使用联合提供转换方向。 / p>
#include <cstdint>
#include <cstddef>
#include <utility>
#include <array>
#include <tuple>
#include <cassert>
#include <cstring>
namespace std {
enum class endian { // since c++20
#ifdef _WIN32
little = 0,
big = 1,
native = little
#else
little = __ORDER_LITTLE_ENDIAN__,
big = __ORDER_BIG_ENDIAN__,
native = __BYTE_ORDER__
#endif
};
namespace literals {
constexpr byte operator"" _byte(unsigned long long v) { // <> Literal-Operator for suffix `_byte`
return byte{static_cast<uint8_t>(v)};
}
}
}
template<typename T>
requires (std::is_same<uint64_t, T>::value || std::is_same<uint32_t, T>::value || std::is_same<uint16_t, T>::value) && (sizeof(T) > 1)
struct ByteUnion<ByteToIntegral, T> {
public:
template<typename... B>
requires (std::is_same<B, std::byte>::value || ...) && (sizeof...(B) == sizeof(T))
explicit ByteUnion(B... vv) : ByteUnion{endian_type{}, std::index_sequence_for<B...>{}, vv...} {}
const T& value() const {return a;}
private:
template<size_t... II, typename... B>
explicit ByteUnion(LittleEndian, std::index_sequence<II...>, B&&... vv) {
uint8_t* p = reinterpret_cast<uint8_t*>(&a);
((p[sizeof...(II) - 1 - II] = static_cast<uint8_t>(vv)),...);
}
template<size_t... II, typename... B>
explicit ByteUnion(BigEndian, std::index_sequence<II...>, B&&... vv) {
uint8_t* p = reinterpret_cast<uint8_t*>(&a);
((p[II] = static_cast<uint8_t>(vv)),...);
}
T a;
};
using namespace std::literals;
int main(){
ByteUnion<IntegralToByte, uint64_t> u1{0x01020304}; // initialise and activate byte-memebers
// ByteUnion<ByteToIntegral, uint64_t> u1x{0x01020304}; // not possible
auto v1 = std::to_integer<uint8_t>(u1[0]);
// u1.value(); // not possible, who access inactive member
assert(v1 == 4);
ByteUnion<ByteToIntegral, uint32_t> u2{0x01_byte, 0x02_byte, 0x03_byte, 0x04_byte}; // initialise and activate integral value member
// ByteUnion<IntegralToByte, uint32_t> u2x{0x01_byte, 0x02_byte, 0x03_byte, 0x04_byte}; // not possible
auto v2 = u2.value();
assert(v2 == 0x01020304);
// u2[0];
}