严格的别名规则uint8_t缓冲到结构

时间:2019-01-17 13:29:07

标签: c

考虑一下,我有一个带有位域的typedef,如下所示。

typedef struct VIN_oCAN01_3abd61be
{
   uint64_t var1:24;
   uint64_t var2:4;
   uint64_t var3:4
}__attribute__((packed))Message;

然后我收到uint8_t缓冲区,如下所示。

uint8_t buffer[4] = {0x1,0x2,0x3,0x14};

当前,在我的生产程序中,我的团队建议采用以下方法。

Message *ptrMsg = (Message *)buffer;

也就是说,将uint8_t缓冲区分配给Message类型的指针。 我曾建议提议的方法不遵循严格别名规则,而是应按以下步骤进行。

Message msg;
memcpy(&msg,buffer, sizeof(msg));
  

请注意,没有选择将缓冲区复制到结构的选项   手动(逐个成员),因为结构很大。

我的理解正确吗?如果可以,请提供标准的文档,我可以用来证明我的观点。

2 个答案:

答案 0 :(得分:5)

  

我曾建议提议的方法不遵循严格的别名规则

正确。 ptrMsg = (Message *)buffer意味着您必须调用未定义的行为才能访问ptrMsg的数据。

您可以使用C17 6.5§7(cited here - What is the strict aliasing rule?)证明自己的观点。像ptrMsg->var1 = value这样的左值表达式不会通过与存储在其中的有效类型兼容的类型或任何允许的表达式来访问存储的值。

不过,您可以从Messageuint8_t的数组(假设uint8_t是字符类型)而不会违反严格的别名。


然而,更大的问题首先是位字段的出现,这是非标准且不可移植的。例如,您无法知道位字段的哪一部分是MSB和LSB。您不知道这些位在64位类型中如何对齐。对位字段使用64位类型是非标准扩展。它依赖于疾病。依此类推。


假设24位是指第31至8位(我们无法通过阅读代码来知道),那么没有严格的别名冲突,位域疯狂和非标准的“结构填充杀手”的正确代码将如下所示: :

typedef union
{
   uint32_t var;
   uint8_t  bytes[4];
} Message;


uint8_t buffer[4];
Message* ptrMsg = (Message*)buffer;
uint32_t var1 = (ptrMsg->var >> 8);
uint8_t  var2 = (ptrMsg->var >> 4) & 0x0F;
uint8_t  var3 = (ptrMsg->var) & 0x0F;

Message是“一个联合类型,其中包括上述类型之一 成员”。这意味着它包含与uint8_t [4]兼容的类型。

此代码也没有复制,并且移位将转换为机器代码中的相关位访问权限。

答案 1 :(得分:4)

你是对的。

C17草案6.5:

  
      
  1. 对象的存储值只能由具有以下任一值的左值表达式访问:   以下类型: 89)

         
        
    • 与对象的有效类型兼容的类型

    •   
    • 与对象的有效类型兼容的类型的合格版本

    •   
    • 一种类型,它是与对象的有效类型相对应的有符号或无符号类型

    •   
    • 一种类型,它是与有效版本的合格版本相对应的有符号或无符号类型   对象的类型

    •   
    • 在其成员中包括上述类型之一的集合或联合类型   (包括递归地包含子集合或所包含的联盟的成员),或
    •   
    • 一种字符类型。
    •   
  2.   

在这种情况下,对象的类型为uint8_t[],左值表达式的类型为Message。以上所有情况均不适用。

在解引用上使用memcpy可解决此问题,或者,如果要使用非C语言编写,则可以在编译器中禁用严格的别名。

但是,即使在此之后,代码仍然存在很多问题并且无法移植:位域在标准中定义得非常差,整体结构是处理序列化的笨拙方式。您应该选择手动反序列化每个成员。