所以,关于这个问题有一些关于SO的问题,但是我还没有找到能够完全回答我想到的问题的东西。首先是一些背景:
我想有一个uint32_t
字段,我也可以将其作为字节数组访问。
所以首先想到的是:
union U {
uint32_t u32;
uint8_t bytes[sizeof(uint32_t)];
};
这允许我这样做:
// "works", but is UB as far as I understand
U u;
u.u32 = 0x11223344;
u.bytes[0] = 0x55;
好的,所以未定义的行为(UB)很糟糕,因此我们不想这样做。类似的强制转换是UB,有时由于对齐问题而更糟糕(虽然不是因为我在我的数组中使用了char
大小的对象)。
// "works", but is UB as far as I understand
uint32_t v = 0x11223344;
auto p = reinterpret_cast<uint8_t *>(&v);
p[0] = 0x55;
再一次,UB很糟糕,因此我们不想这样做。
如果我们使用char*
代替uint8_t*
,有人会说这没关系:
// "works", but maybe is UB?
uint32_t v = 0x11223344;
auto p = reinterpret_cast<char *>(&v);
p[0] = 0x55;
但老实说我不确定...所以要有创意。
所以,我想想我记得将void*
演员的内容读到char*
是合法的(据我所知)(这样就可以了std::memcpy
不是UB)。所以也许我们可以玩这个:
uint8_t get_byte(const void *p, size_t n) {
auto ptr = static_cast<const char *>(p);
return ptr[n];
}
void set_byte(void *p, size_t index, uint8_t v) {
auto ptr = static_cast<char *>(p);
ptr[index] = v;
}
// "works", is this UB?
uint32_t v = 0x11223344;
uint8_t v1 = get_byte(&v, 0); // read
set_byte(&v, 0, 0x55); // write
所以我的问题是:
最后一个例子我想出了UB吗?
如果是,那么“正确”的方法是什么?我真的希望“正确”的方式不是来自字节数组的memcpy
。那太荒谬了。
(奖励):假设我希望我的get_byte返回引用(就像实现operator[]
一样。使用uint8_t
代替文字{是否安全{1}}在阅读char
?
注意:我理解有关字节序和可移植性的问题。它们对我的用例来说不是问题。我认为结果是一个“未指定的值”是可以接受的(因为它是编译器特定的,它将读取哪个字节)。我的问题主要集中在UB方面(“鼻子恶魔”等)。
答案 0 :(得分:3)
为什么不为此创建一个类?
类似的东西:
class MyInt32 {
public:
std::uint32_t asInt32() const {
return b[0]
| (b[1] << 8)
| (b[2] << 16)
| (b[3] << 24);
}
void setInt32(std::uint32 i) {
b[0] = (i & 0xFF);
b[1] = ((i >> 8) & 0xFF);
b[2] = ((i >> 16) & 0xFF);
b[3] = ((i >> 24) & 0xFF);
}
const std::array<std::uint8_t, 4u>& asInt8() const { return b; }
std::array<std::uint8_t, 4u>& asInt8() { return b; }
void setInt8s(const std::array<std::uint8_t, 4u>& a) { b = a; }
private:
std::array<std::uint8_t, 4u> b;
};
所以你没有UB,你没有打破别名规则,你可以根据需要管理endianess。
答案 1 :(得分:0)
这是完全合法的(只要类型是POD),并且uint8_t
不保证是合法的,所以不要。