C++ 联合成员访问和未定义行为
C++ Union Member Access And Undefined Behaviour
我目前正在从事一个项目,其中提供了以下内容
结构。我的工作是 C++,但该项目同时使用 C 和 C++。结构相同
C 和 C++ 都使用定义。
typedef struct PacketHeader {
//Byte 0
uint8_t bRes :4;
uint8_t bEmpty :1;
uint8_t bWait :1;
uint8_t bErr :1;
uint8_t bEnable :1;
//Byte 1
uint8_t bInst :4;
uint8_t bCount :3;
uint8_t bRres :1;
//Bytes 2, 3
union {
uint16_t wId; /* Needed for Endian swapping */
struct{
uint16_t wMake :4;
uint16_t wMod :12;
};
};
} PacketHeader;
根据结构实例的使用方式,所需的字节顺序
该结构可以是大端或小端。作为前两个字节
结构都是单个字节,这些在字节序时不需要改变
变化。
字节 2 和 3,存储为单个 uint16_t
,是我们唯一需要的字节
交换以实现所需的字节顺序。为了实现字节顺序交换,我们有
一直在执行以下操作:
//Returns a constructed instance of PacketHeader with relevant fields set and the provided counter value
PacketHeader myHeader = mmt::BuildPacketHeader(count);
uint16_t packetIdFlipped;
//Swap positions of byte 2 and 3
packetIdFlipped = myHeader.wId << 8;
packetIdFlipped |= (uint16_t)myHeader.wId >> 8;
myHeader.wId = packetIdFlipped;
函数 BuildPacketHeader(uint8_t)
为成员 wMake
赋值,并且
wMod
显式,并且 不 写入成员 wId
。我的问题是关于
从返回的实例中的成员 wId
读取的安全性
结构。
诸如此类的问题
Accessing inactive union member and undefined behavior?,
Purpose of Unions in C and C++,
和 Section 10.4 of the draft standard I have 都提到了在 C++ 中访问非活动联合成员所引起的未定义行为。
链接草案第 10.4 节的第 1 段还包含以下说明,但我不确定我是否理解所有使用的术语:
[Note: One special guarantee is made in order to simplify the use of unions: If a standard-layout union contains several standard-layout structs that share a common initial sequence (10.3), and if a non-static datamember of an object of this standard-layout union type is active and is one of the standard-layout structs, itis permitted to inspect the common initial sequence of any of the standard-layout struct members; see 10.3.— end note]
在 packetIdFlipped = myHeader.wId << 8
行中读取 myHeader.wId
是未定义的行为吗?
未命名的结构是否是活动成员,因为它是函数调用中写入的最后一个成员?
或者注释是否意味着访问 wId
成员是安全的,因为它和结构共享一个公共类型? (这就是通用初始序列的意思吗?)
提前致谢
Is reading myHeader.wId
in the line packetIdFlipped = myHeader.wId << 8
undefined behaviour?
是的。您分配给 wMake
和 wMod
使未命名的结构成为活动成员,因此 wId
是非活动成员,您不能在不为其设置值的情况下读取它。
and is this what is meant by common initial sequence?
common initial sequence is when two standard layout types 成员相同,顺序相同。在
struct foo
{
int a;
int b;
};
struct bar
{
int a;
int b;
int c;
};
a
和b
在foo
和bar
中属于同一类型,所以它们是它们的公共初始序列。如果你将 foo
和 bar
的对象放在一个联合中,那么在其中一个对象中设置了 a
或 b
之后,从凋零对象中读取它是安全的。
这不是你的情况,因为 wId
不是标准布局类型结构。
The function BuildPacketHeader(uint8_t) assigns values to the members
wMake and wMod explicitly, and does not write to the member wId. My
question is regarding the safety of reading from the member wId inside
the returned instance of the structure.
是的,是 UB。这并不意味着它不起作用,只是它可能不起作用。您可以在 BuildPacketHeader 中使用 memcpy 来避免这种情况(参见 this and )。
C++ 标准所说的是给定两个结构 A 和 B 以及以下联合:
union U
{
A a;
B b;
};
以下是有效代码:
U u;
A a;
u.a = a;
a = u.a;
B b;
u.b = b;
b = u.b;
你读写同类型。这显然是正确的代码。
但是当你有以下代码时,问题就来了:
A a;
B b;
u.a = a;
b = u.b;
我们对 A 和 B 了解多少?首先在 union 中他们共享相同的内存 space。现在 C++ 标准已将其明确声明为未定义行为。
但这并不意味着它完全脱离了window。 C99 开始发挥作用,因为它是规范基础并且对工会的保证很弱。也就是说,如果联合成员具有相同的内存布局,它们是兼容的,并且每个结构的第一个内存地址是相同的。所以如果你能确保你的结构/联合成员都以正确的方式填充,操作是安全的,即使 C++ 说它是未定义的。
最后,从实用的角度来看,如果您不乱用填充并获得标准布局,编译器通常会做正确的事情,因为这是 C 中相当古老的使用模式,打破它会破坏很多代码。
我目前正在从事一个项目,其中提供了以下内容 结构。我的工作是 C++,但该项目同时使用 C 和 C++。结构相同 C 和 C++ 都使用定义。
typedef struct PacketHeader {
//Byte 0
uint8_t bRes :4;
uint8_t bEmpty :1;
uint8_t bWait :1;
uint8_t bErr :1;
uint8_t bEnable :1;
//Byte 1
uint8_t bInst :4;
uint8_t bCount :3;
uint8_t bRres :1;
//Bytes 2, 3
union {
uint16_t wId; /* Needed for Endian swapping */
struct{
uint16_t wMake :4;
uint16_t wMod :12;
};
};
} PacketHeader;
根据结构实例的使用方式,所需的字节顺序
该结构可以是大端或小端。作为前两个字节
结构都是单个字节,这些在字节序时不需要改变
变化。
字节 2 和 3,存储为单个 uint16_t
,是我们唯一需要的字节
交换以实现所需的字节顺序。为了实现字节顺序交换,我们有
一直在执行以下操作:
//Returns a constructed instance of PacketHeader with relevant fields set and the provided counter value
PacketHeader myHeader = mmt::BuildPacketHeader(count);
uint16_t packetIdFlipped;
//Swap positions of byte 2 and 3
packetIdFlipped = myHeader.wId << 8;
packetIdFlipped |= (uint16_t)myHeader.wId >> 8;
myHeader.wId = packetIdFlipped;
函数 BuildPacketHeader(uint8_t)
为成员 wMake
赋值,并且
wMod
显式,并且 不 写入成员 wId
。我的问题是关于
从返回的实例中的成员 wId
读取的安全性
结构。
诸如此类的问题 Accessing inactive union member and undefined behavior?, Purpose of Unions in C and C++, 和 Section 10.4 of the draft standard I have 都提到了在 C++ 中访问非活动联合成员所引起的未定义行为。
链接草案第 10.4 节的第 1 段还包含以下说明,但我不确定我是否理解所有使用的术语:
[Note: One special guarantee is made in order to simplify the use of unions: If a standard-layout union contains several standard-layout structs that share a common initial sequence (10.3), and if a non-static datamember of an object of this standard-layout union type is active and is one of the standard-layout structs, itis permitted to inspect the common initial sequence of any of the standard-layout struct members; see 10.3.— end note]
在 packetIdFlipped = myHeader.wId << 8
行中读取 myHeader.wId
是未定义的行为吗?
未命名的结构是否是活动成员,因为它是函数调用中写入的最后一个成员?
或者注释是否意味着访问 wId
成员是安全的,因为它和结构共享一个公共类型? (这就是通用初始序列的意思吗?)
提前致谢
Is reading
myHeader.wId
in the linepacketIdFlipped = myHeader.wId << 8
undefined behaviour?
是的。您分配给 wMake
和 wMod
使未命名的结构成为活动成员,因此 wId
是非活动成员,您不能在不为其设置值的情况下读取它。
and is this what is meant by common initial sequence?
common initial sequence is when two standard layout types 成员相同,顺序相同。在
struct foo
{
int a;
int b;
};
struct bar
{
int a;
int b;
int c;
};
a
和b
在foo
和bar
中属于同一类型,所以它们是它们的公共初始序列。如果你将 foo
和 bar
的对象放在一个联合中,那么在其中一个对象中设置了 a
或 b
之后,从凋零对象中读取它是安全的。
这不是你的情况,因为 wId
不是标准布局类型结构。
The function BuildPacketHeader(uint8_t) assigns values to the members wMake and wMod explicitly, and does not write to the member wId. My question is regarding the safety of reading from the member wId inside the returned instance of the structure.
是的,是 UB。这并不意味着它不起作用,只是它可能不起作用。您可以在 BuildPacketHeader 中使用 memcpy 来避免这种情况(参见 this and
C++ 标准所说的是给定两个结构 A 和 B 以及以下联合:
union U
{
A a;
B b;
};
以下是有效代码:
U u;
A a;
u.a = a;
a = u.a;
B b;
u.b = b;
b = u.b;
你读写同类型。这显然是正确的代码。
但是当你有以下代码时,问题就来了:
A a;
B b;
u.a = a;
b = u.b;
我们对 A 和 B 了解多少?首先在 union 中他们共享相同的内存 space。现在 C++ 标准已将其明确声明为未定义行为。
但这并不意味着它完全脱离了window。 C99 开始发挥作用,因为它是规范基础并且对工会的保证很弱。也就是说,如果联合成员具有相同的内存布局,它们是兼容的,并且每个结构的第一个内存地址是相同的。所以如果你能确保你的结构/联合成员都以正确的方式填充,操作是安全的,即使 C++ 说它是未定义的。
最后,从实用的角度来看,如果您不乱用填充并获得标准布局,编译器通常会做正确的事情,因为这是 C 中相当古老的使用模式,打破它会破坏很多代码。