有没有办法让GCC / Clang知道C中的inheritance?

我正在编写一个C库,它使用一些简单的面向对象的inheritance,如下所示:

struct Base { int x; }; struct Derived { struct Base base; int y; }; 

现在我想将Derived *传递给一个带有Base *的函数:

 int getx(struct Base *arg) { return arg->x; }; int main() { struct Derived d; return getx(&d); }; 

这是有效的,当然是类型安全的,但编译器不知道这一点。 有没有办法告诉编译器这是类型安全的? 我只关注GCC并在这里铿锵作响,因此欢迎编译器特定的答案。 我有一些模糊的记忆,看到一些代码使用__attribute__((inherits(Base))或类似的东西,但我的记忆可能在撒谎。

这在C中是安全的,除了你应该将参数转换为Base * 。 禁止混淆的规则(或更准确地说,不包括在标准C中支持混淆)的规则在C 2011 6.5中,其中第7段规定:

对象的存储值只能由具有以下类型之一的左值表达式访问:

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

此规则阻止我们将指针指向float ,将其转换为指向int的指针,并取消引用指向int的指针以将指针作为int访问。 (更确切地说,它并不妨碍我们尝试,但它会使行为不明确。)

您的代码似乎违反了这一点,因为它使用Base左值访问Derived对象。 但是,C 2011 6.7.2.1第15段规定了将指向Derived的指针转换为指向Base的指针:

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

因此,当我们将指向Derived的指针转换为指向Base的指针时,我们实际拥有的不是指向Derived对象的指针使用与它不同的类型(禁止),而是指向Derived对象的第一个成员的指针使用它的实际类型Base ,这是非常好的。

关于编辑:最初我声明函数参数将被转换为参数类型。 但是,C 6.5.2.2 2要求每个参数都有一个类型,该类型可以分配给具有相应参数类型的对象(删除const任何限定条件),6.5.16.1要求在将一个指针指向另一个时,它们具有兼容的类型(或满足其他不适用的条件)。 因此,将指向Derived的指针传递给采用Base指针的函数会违反标准C约束。 但是,如果您自己执行转换,则它是合法的。 如果需要,可以将转换内置到调用函数的预处理器宏中,这样代码看起来仍然像一个简单的函数调用。

给出基本成员的地址(真正的类型安全选项):

 getx(&d.base); 

或者使用void指针:

 int getx(void * arg) { struct Base * temp = arg; return temp->x; }; int main() { struct Derived d; return getx(&d); }; 

它的工作原理是因为C要求在第一个struct成员之前永远不会有填充。 这不会增加类型安全性,但会消除对铸造的需求。

如上所述user694733,您可能最好通过使用基本字段的地址来符合标准和类型安全性(重复以供将来参考)

 struct Base{ int x; } struct Derived{ int y; struct Base b; /* look mam, not the first field! */ } struct Derived d = {0}, *pd = &d; void getx (struct Base* b); 

而现在尽管基地不是第一个你仍然可以做的领域

 getx (&d.b); 

或者如果你正在处理指针

 getx(&pd->b). 

这是一个非常普遍的习语。 但是,如果指针为NULL,则必须小心,因为&pd-> b就是这样

 (struct Base*)((char*)pd + offsetof(struct Derived, b)) 

so&((Derived *)NULL) – > b成为

 ((struct Base*)offsetof(struct Derived, b)) != NULL. 

IMO是一个错过的机会,C采用了匿名结构,但没有采用计划9匿名结构模型

 struct Derived{ int y; struct Base; /* look mam, no fieldname */ } d; 

它允许您只编写getx(&d),编译器会将Derived指针调整为基指针,即它与上面示例中的getx(&d.b)完全相同。 换句话说,它有效地为您提供inheritance,但具有非常具体的内存布局模型。 特别是,如果你坚持不在顶层嵌入(==inheritance)基础结构,你必须自己处理NULL。 正如您对inheritance所期望的那样,它可以递归地工作

 struct TwiceDerived{ struct Derived; int z; } td; 

你仍然可以写getx(&td)。 此外,您可能不需要getx,因为您可以编写dx(或td.x或pd-> x)。

最后使用typeof gcc扩展,你可以编写一个用于向下转换的小宏(即转换为更加派生的结构)

 #define TO(T,p) \ ({ \ typeof(p) nil = (T*)0; \ (T*)((char*)p - ((char*)nil - (char*)0)); \ }) \ 

所以你可以做的事情

 struct Base b = {0}, *pb = &b; struct Derived* pd = TO(struct Derived, pb); 

如果您尝试使用函数指针执行虚函数,这将非常有用。

在gcc上,您可以使用-fplan9-extensions来使用/试验计划9扩展。 不幸的是,它似乎没有在clang上实现。