通过在作为另一个结构的一个结构而不是第一个成员的结构之间转换指针来实现C中的inheritance是否合法?

现在我知道我可以通过将指向struct的指针强制转换为该struct的第一个成员的类型来实现inheritance。

然而,纯粹作为一种学习经验,我开始想知道是否有可能以稍微不同的方式实现inheritance。

这段代码合法吗?

 #include  #include  struct base { double some; char space_for_subclasses[]; }; struct derived { double some; int value; }; int main(void) { struct base *b = malloc(sizeof(struct derived)); b->some = 123.456; struct derived *d = (struct derived*)(b); d->value = 4; struct base *bb = (struct base*)(d); printf("%f\t%f\t%d\n", d->some, bb->some, d->value); return 0; } 

这段代码似乎产生了预期的结果 ,但正如我们所知,这远非certificate它不是UB。

我怀疑这样的代码可能合法的原因是我看不到任何可能出现的对齐问题。 但是,当然这远远不知道没有出现这样的问题,即使确实没有对齐问题,代码可能仍然是任何其他原因的UB。

  • 以上代码有效吗?
  • 如果不是,有没有办法让它有效?
  • char space_for_subclasses[]; 必要? 删除此行后,代码似乎仍然表现得很好

这或多或少与struct sockaddr使用的穷人的inheritance相同,并且对于当前一代的编译器来说它是可靠的。 演示问题的最简单方法是这样的:

 #include  #include  #include  struct base { double some; char space_for_subclasses[]; }; struct derived { double some; int value; }; double test(struct base *a, struct derived *b) { a->some = 1.0; b->some = 2.0; return a->some; } int main(void) { void *block = malloc(sizeof(struct derived)); if (!block) { perror("malloc"); return 1; } double x = test(block, block); printf("x=%g some=%g\n", x, *(double *)block); return 0; } 

如果标准字母允许a->someb->some为同一个对象,那么这个程序将被要求打印x=2.0 some=2.0 ,但是有些编译器并且在某些条件下(它赢了’ t发生在所有优化级别,您可能必须将test移动到自己的文件)它将打印x=1.0 some=2.0

标准的字母是否允许a->someb->some是同一个对象是有争议的。 请参阅http://blog.regehr.org/archives/1466及其链接的论文。

当我阅读标准时,章节§6.2.6.1/ P5,

某些对象表示不需要表示对象类型的值。 如果对象的存储值具有这样的表示,并且由不具有字符类型的左值表达式读取,则行为是未定义的。 […]

因此,只要space_for_subclasseschararray-decays-to-pointer )成员并且您使用它来读取值,您应该没问题。


那说,回答

char space_for_subclasses[]; 必要?

是的,确实如此。

引用§6.7.2.1/ P18,

作为一种特殊情况,具有多个命名成员的结构的最后一个元素可能具有不完整的数组类型; 这称为灵活的arrays成员 。 在大多数情况下,忽略灵活的数组成员。 特别地,结构的尺寸好像省略了柔性arrays构件,除了它可以具有比遗漏所暗示的更多的拖尾填充。 但是,当一个. (或-> )运算符有一个左操作数,它是一个带有灵活数组成员的结构(指针),右操作数命名该成员,它的行为就好像该成员被最长的数组替换(具有相同的元素类型) )不会使结构大于被访问的对象; 数组的偏移量应保持为灵活数组成员的偏移量,即使这与替换数组的偏移量不同。 如果此数组没有元素,则其行为就好像它有一个元素,但如果任何尝试访问该元素或生成一个经过它的指针,则行为是未定义的。

删除它,你将访问无效的内存,导致未定义的行为 。 但是,在您的情况下(第二个代码段),您无论如何都无法访问value ,因此这不会成为问题。