这个工会合法吗?

Is this union legal?

我正在修改和清理前同事的代码。我创建了一个联合体,通过他设计的UART传输协议来简化消息的“解析”和“构造”。

示例消息:!cx:000:2047

第一个字符表示参数(由第二个和第三个字符定义,在本例中为cx)是否应该设置(!)或返回(?)到消息的发送者。如果需要,消息的地址部分 (000) 定义数组中的索引,指定值(在本例中为 2047)应写入该数组。命令、地址和值使用 : 字符分隔。

我本可以使用 strtok() 使用分隔符来分隔消息,但接收缓冲区的大小固定为 12 个字符,发件人功能确保消息的格式正确,我找到了这个方法是最容易阅读和理解的。

但是现在,我看到了 this 话题,我不确定下面的联合是否合法:

typedef union UartMessage
{
    char msg[12];
    struct
    {
        char dir;           //[0]:      set/get
        char param[2];      //[1-2]:    parameter to set/get
        char separator1;    //[3]:      ':' separator
        char addr[3];       //[4-6]:    memory array index (used for current and speed arrays)
        char separator2;    //[7]:      ':' separator
        char value[4];      //[8-11]:   value to set
    };
} uartMsg_t;

自 C11 起添加匿名结构是合法的。

但是,如果嵌入式结构中没有引入额外的填充,我建议添加一个断言。

_Static_assert(offsetof(uartMsg_t, value[4]) == 12, "extra padding");

C 和 C++ 在“联合类型双关”方面是不同的。在 C 中,我们可能写入一个联合成员并从另一个成员读取,之后二进制值被转换为新类型。假设对齐没有问题,并且可以根据值表示新类型,这就是 C 中的 well-defined1)。在 C++ 中执行相同操作会调用未定义的行为.

至于你的union在用C编译时能不能用,这取决于对齐方式。仅由 char 成员(或它们的数组)组成的 struct/union 不应该有任何填充,尽管理论上编译器可以自由插入它。最佳做法包括进行 compile-time 检查以确保这不是问题:

_Static_assert(sizeof(uartMsg_t) == sizeof((uartMsg_t){0}.msg), 
               "Padding detected!");

在其他情况下,当您确实有填充时,您可能必须使用 non-standard #pragma pack 或类似的方式明确禁用它。使用结构表示数据协议的最大可移植程序需要调用显式 seralization/deserialization 例程(可移植性优于性能)。


1) 来源:C17 6.5.2.3+注97), C17 6.2.6