输入双关和工会

时间:2015-10-06 17:43:51

标签: c++ c++11 casting unions type-punning

所以,关于这个问题有一些关于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

所以我的问题是:

  1. 最后一个例子我想出了UB吗?

  2. 如果是,那么“正确”的方法是什么?我真的希望“正确”的方式不是来自字节数组的memcpy。那太荒谬了。

  3. (奖励):假设我希望我的get_byte返回引用(就像实现operator[]一样。使用uint8_t代替文字{是否安全{1}}在阅读char

  4. 的内容时

    注意:我理解有关字节序和可移植性的问题。它们对我的用例来说不是问题。我认为结果是一个“未指定的值”是可以接受的(因为它是编译器特定的,它将读取哪个字节)。我的问题主要集中在UB方面(“鼻子恶魔”等)。

2 个答案:

答案 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不保证是合法的,所以不要。