C ++工会成员访问和未定义的行为

时间:2019-03-21 13:35:53

标签: c++ undefined-behavior unions

我目前正在一个项目中,向我提供以下内容 结构。我的工作是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;

根据结构实例的使用方式,所需的字节序 结构可以是大端或小端。作为前两个字节 结构每个都是单个字节,这些字节的字节序不需要更改 变化。 存储为单个uint16_t的字节2和3是我们需要的唯一字节 交换以达到所需的字节序。为了实现字节序交换,我们有 一直在执行以下操作:

//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段也包含以下注释,尽管我不确定我是否了解所使用的所有术语:

  

[注意:为了简化并集的使用,做出了一项特殊保证:如果标准布局并集包含多个共享公共初始序列(10.3)的标准布局结构,并且如果非静态数据成员为此标准布局联合类型的对象是活动的,并且是标准布局结构之一,允许检查任何标准布局结构成员的公共初始序列;参见10.3.—尾注]

是否在myHeader.wId行中读取packetIdFlipped = myHeader.wId << 8的未定义行为?

未命名结构是否是活动成员,因为它是函数调用中最后写入的成员?

还是该注释表示访问wId成员是安全的,因为它和结构共享一个公共类型? (这是常见初始序列的意思吗?)

预先感谢

3 个答案:

答案 0 :(得分:4)

  

函数BuildPacketHeader(uint8_t)将值分配给成员   wMake和wMod显式,并且不写入成员wId。我的   问题是关于从内部成员wId读取数据的安全性   返回的结构实例。

是的,是UB。这并不意味着它不起作用,只是它可能不起作用。您可以在BuildPacketHeader中使用memcpy来避免这种情况(请参阅thisthis)。

答案 1 :(得分:1)

  

是否在myHeader.wId行中读取packetIdFlipped = myHeader.wId << 8的未定义行为?

是的。您已分配给wMakewMod来使未修饰的结构成为活动成员,因此wId是非活动成员,并且如果不对其设置值,则不允许您读取它。

  

这是常见初始序列的意思吗?

common initial sequence是两个standard layout types以相同顺序共享相同成员的时候。在

struct foo
{
    int a;
    int b;
};

struct bar
{
    int a;
    int b;
    int c;
};

abfoobar中具有相同的类型,因此它们是它们的共同初始序列。如果将foobar的对象放在一个并集中,则将其中的一个设置为凋零对象后,可以安全地读取ab

这不是您的情况,因为wId不是标准的布局类型结构。

答案 2 :(得分:1)

C ++标准类型说的是给定的两个结构A和B以及以下内容:

private ShipmentRepository shipmentRepository;

以下是有效代码:

public void add(Shipment shipment)

您读写相同的类型。这显然是正确的代码。

但是,当您使用以下代码时,就会出现问题:

union U
{
  A a;
  B b;
};

我们对A和B有什么了解?首先,它们共享相同的内存空间。现在,C ++标准已将其明确声明为未定义的行为。

但这并不意味着它完全不在窗口之内。 C99发挥了作用,因为它是规范基础,并且对工会的保证很弱。也就是说,如果联合成员具有相同的内存布局,则它们是兼容的,并且每个结构的第一个内存地址都相同。因此,即使您可以确保以正确的方式填充所有struct / union成员,也可以保证操作安全,即使C ++表示未定义。

最后,从务实的角度来看,如果您不喜欢填充并获得标准布局,则编译器通常会做正确的事,因为这是C语言中的一种很老的用法模式,破坏它会破坏很多代码