在C中扩展结构

我最近遇到了一个像这样的同事代码:

typedef struct A { int x; }A; typedef struct B { A a; int d; }B; void fn(){ B *b; ((A*)b)->x = 10; } 

他的解释是,由于struct Astruct B的第一个成员,因此b->x将与b->ax相同,并提供更好的可读性。
这是有道理的,但这是否被认为是良好的做法? 这将跨平台工作吗? 目前这在GCC上运行良好。

是的,它将跨平台工作,但这并不一定是一个好主意。

根据ISO C标准(以下所有引用来自C11), 6.7.2.1 Structure and union specifiers /15 ,不允许结构的第一个元素之前填充

另外, 6.2.7 Compatible type and composite type规定:

如果types相同,则两种types具有兼容types

AA-within-Btypes是相同的,这是毫无争议的。

这意味着访问A字段的内存将在ABtypes中都是相同的,如果您将来对可维护性有任何顾虑,则可能是您应该使用的更为明智的b->ax

而且,虽然您通常不得不担心严格的types别名,但我不认为这适用于此。 别名指针非法的,但标准有特定的例外。

6.5 Expressions /7表示其中的一些例外情况,注脚:

这个列表的目的是指定一个对象可能被别名或不被别名的情况。

列出的例外是:

  • a type compatible with the effective type of the object ;
  • 在这里不需要关注的其他一些例外; 和
  • an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union)

结合上面提到的struct padding规则,包括这个短语:

指向适当转换的结构对象的指针指向其初始成员

似乎表明这个例子是特别允许的。 这里我们要记住的核心是expression式((A*)b)A* ,而不是B* 。 这使得这些variables兼容于无限制混叠的目的。

这是我对标准相关部分的阅读,在(a)之前我错 ,但在这种情况下,我怀疑它。

所以,如果你真的需要这个,那么它会工作的很好,但是我会将代码中的任何约束logging在非常接近结构的代码中,以免将来被咬伤。


(一)由于我的妻子会告诉你,经常和没有太多的提示:-)

我会在这个问题上站出来反对@paxdiablo :我认为这是一个好主意,而且在大量生产质量的代码中这是很常见的。

这基本上是在C中实现基于inheritance的面向对象的数据结构的最明显和最好的方式。用struct A的实例开始struct B的声明意味着“B是struct A的子类”。 第一个结构成员从结构的开始保证为0字节的事实是使得它安全地工作的事实,并且在我看来这是边缘美丽的。

它被广泛使用和部署在基于GObject库的代码中,比如GTK +用户界面工具包和GNOME桌面环境。

当然,它需要你“知道你在做什么”,但是当用C实现复杂的types关系时,情况总是如此。

在GObject和GTK +的情况下,有大量的支持基础设施和文档来帮助解决这个问题:很难忘记它。 这可能意味着创build一个新类并不像在C ++中那样快,但是这也许是可以预料的,因为在类C中没有本地支持。

通常应避免任何绕过types检查的事情。 这种攻击依赖于声明的顺序,并且编译器也不能执行强制转换和这个顺序。

它应该跨平台工作,但我认为这不是一个好的做法。

如果你真的拥有深层次的嵌套结构(你可能不得不想知道为什么),那么你应该使用一个临时的局部variables来访问这些字段:

 A deep_a = e->dcba; deep_a.x = 10; deep_a.y = deep_a.x + 72; e->dcba = deep_a; 

或者,如果你不想复制a

 A* deep_a = &(e->dcba); deep_a->x = 10; deep_a->y = deep_a->x + 72; 

这表明从哪里来,它不需要演员。

Java和C#也经常暴露像“cba”这样的结构,我不明白是什么问题。 如果你想要模拟的是面向对象的行为,那么你应该考虑使用面向对象的语言(比如C ++),因为按照你所提出的方式“扩展结构”并不提供封装和运行时多态性(尽pipe可能会有争议((A *)b)类似于“dynamic演员”)。

这是一个可怕的想法。 只要有人出现在struct B的前面插入另一个字段,程序就会崩溃。 bax什么问题?

我很抱歉不同意这里的所有其他答案,但是这个系统不符合标准C.不能同时使用指向同一位置的不同types的两个指针,这被称为别名,并且是C99等严格的走样规则都是不允许的。 一个不太难看的做法是使用内联的getter函数,这样就不需要那么整洁。 或者这可能是工会的工作? 特别允许持有几种types之一,但也有其他许多缺点。

总之,这种肮脏的铸造创造多态是大多数C标准所不允许的,只是因为它似乎在你的编译器上工作并不意味着它是可以接受的。 请参阅此处,了解不允许的原因,以及处于高优化级别的编译器可能会破坏不遵循这些规则的代码的原因http://en.wikipedia.org/wiki/Aliasing_%28computing%29#Conflicts_with_optimization

是的,它会工作。 它是面向对象使用C的核心原则之一。 有关扩展(即inheritance)的更多示例,请参阅“ C中的面向对象 ”。

这是完全合法的,在我看来,非常优雅。 有关生产代码的示例,请参阅GObject文档 :

由于这些简单的条件,可以通过执行以下操作来检测每个对象实例的types:

 B *b; b->parent.parent.g_class->g_type 

或者更快:

 B *b; ((GTypeInstance*)b)->g_class->g_type 

就我个人而言,我认为工会是丑陋的,往往会导致巨大的switch语句,这是通过编写面向对象代码来避免的。 我用这种风格自己编写了大量的代码 – 通常情况下,结构的第一个成员包含函数指针,可以使其像所讨论的types的虚表一样工作。

我可以看到这是如何工作,但我不会称之为好的做法。 这取决于每个数据结构的字节如何放置在内存中。 无论何时你将一个复杂的数据结构转换成另一个(比如结构体),这不是一个好主意,特别是当这两个结构体的大小不一样时。

我认为OP和许多评论者已经locking了代码扩展结构的想法。

不是这样。

这是作文的例子。 很有用。 (摆脱typedef,这里是一个更具描述性的例子):

 struct person { char name[MAX_STRING + 1]; char address[MAX_STRING + 1]; } struct item { int x; }; struct accessory { int y; }; /* fixed size memory buffer. The Linux kernel is full of embedded structs like this */ struct order { struct person customer; struct item items[MAX_ITEMS]; struct accessory accessories[MAX_ACCESSORIES]; }; void fn(struct order *the_order){ memcpy(the_order->customer.name, DEFAULT_NAME, sizeof(DEFAULT_NAME)); } 

你有一个固定大小的缓冲区,很好地划分。 它确实击败了一个巨大的单层结构。

 struct double_order { struct order order; struct item extra_items[MAX_ITEMS]; struct accessory extra_accessories[MAX_ACCESSORIES]; }; 

所以现在你有第二个结构可以被视为(一个inheritance),就像第一个结果一样。

 struct double_order d; fn((order *)&d); 

这保留了与编写用于较小结构的代码的兼容性。 Linux内核( http://lxr.free-electrons.com/source/include/linux/spi/spi.h (查看struct spi_device))和bsd套接字库( http://beej.us/guide/ bgnet / output / html / multipage / sockaddr_inman.html )使用这种方法。 在内核和套接字的情况下,你有一个通过通用和区分代码段运行的结构。 与inheritance的用例不尽相同。

我不会build议为了可读性而写这样的结构。

我认为Postgres在他们的一些代码中也是这样做的。 这并不是说它是一个好主意,但它确实说了一些似乎是被广泛接受的东西。

也许你可以考虑使用macros来实现这个function,需要重用函数或者字段到macros中。