【问题标题】:Does inheritance via unwinding violate strict aliasing rule?通过展开继承是否违反了严格的别名规则?
【发布时间】:2017-12-08 08:45:05
【问题描述】:

我有一个继承自 struct Base 的 struct X。但是,在我当前的设置中,由于对齐,X 的大小为 24B:

typedef struct {
    double_t a;
    int8_t b;
} Base;

typedef struct {
    Base base;
    int8_t c;
} X;

为了节省内存,我想展开 Base 结构,所以我创建了包含来自 Base 的字段的结构 Y(以相同的顺序,总是在结构的开头),所以结构为 16B:

typedef struct {
    double_t base_a;
    int8_t base_b;
    int8_t c;
} Y;

然后我将在一个方法中使用 struct Y 的实例,该方法需要一个指向 Base struct 的指针:

void print_base(Base* b)
{
  printf("%f %d\n", b->a, b->b);
}
// ...
Y data;
print_base((Base*)&data);

上面的代码是否违反了严格的别名规则,导致了未定义的行为?

【问题讨论】:

  • 对,修复了示例代码
  • 为什么不简单地在Y 中声明Base base; 并用print_base(&data.base); 调用函数呢?它消耗的内存与其他结构中的结构成员相同。
  • @mch 它没有。使用您的方法的 struct X 的大小需要 24 个字节,而 struct Y 只需 16
  • 在我看来,整个 X 类型在这个例子中是无关紧要的。您正在从 Y 投射到与 X 无关的 Base。
  • @Marian 我想介绍正确的方法,并解释为什么我不想这样做。

标签: c inheritance struct strict-aliasing


【解决方案1】:

首先,BaseY 不是标准 6.2.7 定义的兼容类型,所有成员必须匹配。

要通过Base* 访问Y 而不创建严格的别名冲突,Y 需要是在其成员中包含Base 类型的“聚合类型”(它是)。它没有。

所以这是一个严格的别名违规,此外,由于YBase 不兼容,它们可能有不同的内存布局。这就是重点,正是因为这个原因,你才把它们做成了不同的类型:)

在这种情况下,您可以做的是使用具有共享公共初始序列的结构成员的联合,这是一种特殊的允许情况。 C11 6.5.2.3 中的有效代码示例:

union {
  struct {
    int alltypes;
  } n;
  struct {
    int type;
    int intnode;
  } ni;
  struct {
    int type;
    double doublenode;
  } nf;
} u;

u.nf.type = 1;
u.nf.doublenode = 3.14;
/* ... */
if (u.n.alltypes == 1)
  if (sin(u.nf.doublenode) == 0.0)

【讨论】:

  • 谢谢。我不确定内存布局。标准不能保证两个第一个字段(Base 中的 a、b、Y 中的 base_a、base_b)的偏移量相同吗?
  • @MarcinKolny 这与内存布局无关。两种类型的内存布局相同。这是关于通知编译器优化器。您应在标头中定义 union {Base b; Y y;}; 以通知优化器,当值 y.base_a 更改时,他必须随时从内存中重新加载值 b.a
  • @Marian 谢谢,这听起来很有趣。只是好奇,你能给我指出一些我可以阅读更多关于此的文章吗?
  • @MarcinKolny 例如:blog.regehr.org/archives/1307。要在 google 上获得更多“严格别名”。
  • 它是内存布局并通知编译器。编译器可以选择总是在对齐的地址上分配结构,但如果结构被它包含的所有成员替换,则不一定会这样做。填充字节可能有所不同。
猜你喜欢
  • 2020-04-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-09
  • 2016-02-24
  • 2015-01-16
相关资源
最近更新 更多