C++ 类型双关与联合

C++ 类型双关与联合,c++,c++11,casting,unions,type-punning,C++,C++11,Casting,Unions,Type Punning,关于这个问题,有几个问题,但我还没有找到确切的答案。首先是一些背景: 我想要一个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; 好的,所以未

关于这个问题,有几个问题,但我还没有找到确切的答案。首先是一些背景:

我想要一个
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
大小的对象)

但我真的不确定。。。所以要有创意


因此,我认为我记得(据我所知)将
void*
的内容转换为
char*
(这允许
std::memcpy
之类的内容不被使用)是合法的。也许我们可以玩一下这个:

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
uint8获取字节(const void*p,size\t n){
自动ptr=静态(p);
返回ptr[n];
}
void set_字节(void*p,大小索引,uint8_t v){
自动ptr=静态(p);
ptr[指数]=v;
}
//“工作”,这是UB吗?
uint32_t v=0x11223344;
uint8_t v1=获取字节(&v,0);//阅读
设置_字节(&v,0,0x55);//写
因此,我的问题是:

  • 是我最后举的一个例子吗

  • 如果是,那么“正确”的方法是什么?我真的希望“正确”的方法不是从字节数组到
    memcpy
    。那太荒谬了

  • (奖金):假设我希望get_字节返回一个引用(例如用于实现
    运算符[]
    。在读取
    void*
    的内容时,使用
    uint8\u t
    而不是literal
    char
    安全吗


  • 注意:我理解关于endian和可移植性的担忧。它们对我的用例来说不是问题。我认为结果是“未指定的值”是可以接受的(因为它是特定于编译器的,它将读取哪个字节)。我的问题真正集中在UB方面(“鼻魔”和类似内容).

    它是完全合法的(只要类型是POD),而且
    uint8\u t
    不保证合法,所以不要这样做。

    为什么不为此创建一个类呢

    比如:

    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;
    };
    
    类MyInt32{
    公众:
    std::uint32\u t asInt32()常量{
    返回b[0]
    |(b[1]>16)和0xFF);
    b[3]=((i>>24)和0xFF);
    }
    常量std::array&asInt8()常量{return b;}
    std::array&asInt8(){return b;}
    void setInt8s(const std::array&a){b=a;}
    私人:
    std::数组b;
    };
    

    所以你没有UB,你没有破坏别名规则,你可以随心所欲地管理endianess。

    任何你反对
    std::bitset
    memcpy
    的理由都不等同于通过联合进行类型双关。@kfmfe04,
    std::bitset
    不适合我的用例。它允许(代理)访问位,在哪种情况下memcpy不足以通过联合替换类型双关?包括memcpy在内的几个线程表明memcpy就足够了。是否有我遗漏的注意事项?我只看到了一个保证,即如果您强制转换到
    void*
    并返回到原始类型,您将得到相同的指针。将
    void*
    强制转换为其他类型是不正确的。哪一种是合法的?通过转换到
    char*
    使用
    void*
    ?至于
    uint8\t
    它不保证是“字符类型”吗?它不保证是字符类型。它只能保证是一个无符号的8位整数类型。我想我可以。我举的例子有点简化。真正的代码希望以字节/整数的形式读写。当然,我也可以做一些写操作(可能需要像
    std::bitset
    does这样的代理对象),但这是可行的。哇,这是很多字符。遗憾的是,在C++中,正确的方式通常是冗长的:<代码>显式< /代码>,而隐式则更好, <代码> VS可更改,<代码> STD::数组< /COV> VS>代码> T[N] < /代码>,<代码> StasyPPTR V>代码> T*<代码>…
    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
    
    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;
    };