有没有办法让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上实现。