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
。那太荒谬了运算符[]
。在读取void*
的内容时,使用uint8\u t
而不是literalchar
安全吗注意:我理解关于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++中,正确的方式通常是冗长的:<代码>显式< /代码>,而隐式则更好,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;
};