打包和指针别名,C和C ++

时间:2017-09-20 13:23:02

标签: c++ c

union vec
{
#pragma pack(push,1)
   struct
   {
      float x, y, z;
   }
#pragma pack(pop)
   float vals[3];
};

考虑上面的定义。 (C99中的匿名工会除外)

我认为这个答案可能允许不同的答案,具体取决于编译器的选择,语言的选择和标准的选择。

  1. 我相信sizeof(vec) == 3*sizeof(float)
  2. 我保证(通过#pragma编译器文档,而不是语言保证)
  3. 因此,我相信我保证&vec.x == &vec.vals[0],等等。
  4. 但是,我不确定它是否合法(即,不允许通过严格别名),从v.x写入然后从v.vals[0]读取
  5. 包装旁边,我认为相关的措辞(至少从C99标准)是:

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

    •   
    • 包含上述内容之一的聚合或联合类型   其成员之间的类型(包括,递归地,成员   subaggregate或contained union),或

    •   

1 个答案:

答案 0 :(得分:6)

  
      
  1. 我相信我保证(通过#pragma编译器文档,而不是语言保证)sizeof(vec)== 3 * sizeof(float)
  2.   

是的,这是正确的,假设完全禁用#pragma填充。

  
      
  1. 因此,我相信我保证& vec.x ==& vec.vals [0],等等。
  2.   

无论填充如何,都可以保证这一点,因为在struct / union的开头永远不会有填充。参见例如C116.7.2.1§15:

  

结构对象中可能有未命名的填充,但不在其开头。

这适用于C标准的所有版本,据我所知,也适用于所有版本的C ++标准。

  
      
  1. 但是,我不确定它是否合法(即,不允许通过严格别名),从v.x写入然后从v.vals [0]
  2. 读取   

这在C中很好,但在C ++中是未定义的行为。

在C中,. / ->运算符保证了这一点,C11 6.5.2.3:

  

后缀表达式后跟。运算符和标识符指定的成员   结构或联合对象。该值是指定成员的值,95)如果第一个表达式是左值,则它是左值。

脚注95(信息性而非规范性)说:

  

95)如果用于读取union对象内容的成员与上次使用的成员不同   在对象中存储一个值,该值的对象表示的相应部分被重新解释   作为6.2.6中描述的新类型中的对象表示(有时称为''类型的过程)   双关语””)。这可能是陷阱表示。

C ++没有这样的保证,因此通过联合的“类型惩罚”是C ++中未定义的行为。这是两种语言之间的主要区别。

此外,C对于联合也有公共初始序列的概念,也在C11 6.5.2.3中指定:

  

为了简化工会的使用,我们提出了一项特殊保证:如果工会包含   几个结构共享一个共同的初始序列(见下文),如果是联盟   对象当前包含这些结构中的一个,允许检查公共结构   其中任何一个的初始部分都是完整类型的联盟的声明   是可见的。如果相应的成员具有一个或多个序列的兼容类型(并且对于位字段,具有相同的宽度),则两个结构共享公共初始序列   初始成员。

确实,您的示例中的数组和结构可能是别名,因为您引用的部分是“在其成员中包含上述类型之一的聚合或联合类型”。因此,写入结构然后通过数组读取数据不会违反严格的别名,无论是C还是C ++。

然而,C ++在处理联合时具有“活动成员”的概念,因此在C ++中,除了别名之外,由于其他原因,这会给出指定不当的行为 - 即C ++只保证最后一个工会的书面成员可以安全地阅读。