【问题标题】:Strict aliasing rule uint8_t buffer to structure严格的别名规则 uint8_t 缓冲区到结构
【发布时间】:2019-01-17 13:29:06
【问题描述】:

假设我有一个 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));

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

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

【问题讨论】:

  • 除了严格的别名冲突问题之外,还要注意印度性。使用 memcpy 从 char[] 填充 uint 会使您的代码完全不可移植。
  • @AlainMerigot 很好!,我们在 memcpy 或指针分配之前处理字节序,只是我没有在这里显示。
  • 我认为 严格别名 是编译器的事情,通常它有一个标志,如果它可能会或可能不会为不同的优化级别假设严格别名。
  • @user694733 我定义了位字段。
  • 您的位域加起来为 32 位,在 uint64_t 中。您不知道这些位域实际上在那些 64 位中的 where 位置。每6.7.2.1p11:“是否将不适合的位域放入下一个单元或与相邻单元重叠是实现定义的。一个单元内的位域分配顺序(高-order to low-order or low-order to high-order)是实现定义。可寻址存储单元的对齐方式是未指定。"位域对于可移植性和可维护性来说只是个坏消息。

标签: c


【解决方案1】:

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

正确。 ptrMsg = (Message *)buffer 表示你不能访问ptrMsg 的数据而不调用未定义的行为。

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

但是,您可以从 Message 转到 uint8_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] 兼容的类型。

此代码也不包含复制,并且移位将被转换为机器代码中的相关位访问。

【讨论】:

    【解决方案2】:

    你是对的。

    C17 草案第 6.5 节:

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

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

      • 与对象的有效类型兼容的类型的限定版本,

      • 对象的有效类型对应的有符号或无符号类型,

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

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

    在这种情况下,对象的类型是uint8_t[],左值表达式的类型是Message。上述例外均不适用。

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

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

    【讨论】:

    • 平心而论,禁用严格别名并不是一个坏主意。特别是在嵌入式系统设计中,这似乎是。严格别名/有效类型是 C 语言的一个损坏部分,由不掌握嵌入式系统中经常需要的类型双关语的 PC 程序员指定。此外,GCC 是我所知道的唯一一个滥用严格别名来生成更快的代码的编译器,它根本无法执行不知情的程序员的意图。但它的效率很高!最好总是使用gcc -fno-strict-aliasing
    • @Lundin 但另一方面是程序员在违反严格别名会导致实际问题的系统上没有经验。因此,您会遇到类似这样的问题,其中一群显然对严格别名一无所知的程序员似乎促使确实了解其含义的人在 Stackoverflow 上发布问题。
    • @AndrewHenle 但是严格的别名规则并不是真正的常识。需要一个坚定的 C 资深人士/语言书呆子才能了解并理解它。这反过来又使其成为安全隐患和语言缺陷。特别是从之前的 C99 开始,没有编译器依赖它进行积极的优化……C99 花了大约 10 年的时间才成为行业标准。直到 2009 年左右,当 gcc 作为 ARM 编译器开始流行时,我们才真正在嵌入式系统中看到严格的别名错误。因此,非书呆子必须使用 MISRA-C 告诉他们“不要这样做”而不理解为什么。
    猜你喜欢
    • 2018-10-27
    • 2015-10-15
    • 2017-02-25
    • 1970-01-01
    • 1970-01-01
    • 2013-03-11
    • 2018-12-14
    • 2015-05-31
    • 1970-01-01
    相关资源
    最近更新 更多