是否需要不兼容的指针来实现C中的多态性

我尝试使用以下代码在C中模拟C ++的多态性:

#include typedef struct Base { void (*out) (void); } Base; typedef struct Derived { Base base; int x; } Derived; void base_out() { printf("base\n"); } void derived_out() { printf("derived\n"); } void foo(void) { Derived d; Base b; b.out = base_out; d.base.out = derived_out; Base *p; p = &b; p->out(); //warning: assignment from incompatible pointer type p = &d; p->out(); } int main(void) { foo(); } 

但是当p = &d时,我必须做不兼容的指针赋值。 为了使其工作, Derived base成员也必须是第一个成员。 如何避免这两种情况?

不需要不兼容的指针赋值。 Per C 2011 [draft N1570] 6.7.2.1 15,“指向结构对象的指针,经过适当转换,指向其初始成员(或者如果该成员是位字段,则指向它所在的单位),以及反之亦然。“因此,当Derived的第一个元素是Base对象时,可以将指向Derived的指针转换为指向Base的指针。 并且指向作为Derived第一个成员的Base的指针可以转换为指向Derived的指针。

所以这是合法的。 编译器可能会警告你这是安全的,因为它很不寻常。 使用显式强制转换可以避免:

 p = (Base *) &d; 

或者,您可以直接获取基地址:

 p = &d.base; 

但是,从Base返回到Derived仍需要强制转换:

 Derived *x = (Derived *) p; 

至于把基地放在除了作为第一个成员之外的某个地方,那么你就是在更加狡猾的地方。 派生到基地的方向仍然很容易; p = &d.base很好。 换句话说,这可能就像你可以达到法定C一样接近:

  1. 获取指向基数的字符类型指针: char *c = (char *) p 。 在C中,将任何指向对象的指针转换为指向字符类型的指针是合法的,这将为您提供指向对象的第一个字节的指针。

  2. 减去从Derived开始到Base成员的字节数: c -= offsetof(Derived, base) 。 (这需要包括 。)因为c指向Derived Base的第一个字节,它指向Derived中的一个字节,并且你大部分(见下文)允许将Derived视为一个字节数组,所以我们可以减去回到开头。 offsetof宏提供了该字节的字节数。

  3. 将字符指针转换为指向Derived的指针: Derived *x = (Derived *) c 。 在这里,我们处于不稳定的状态。 如果我们将指向Derived的指针转换为字符指针,我们可以立即将其转换回来。 但是我们没有以这种方式得到这个指针。 它指向相同的字节,但我不清楚C标准是否支持这一点。 (实际上,上面的步骤2有一个类似的问题;一个对象可以解释为一个字符数组,但我们是从一个我不确定C标准实际支持的方向来实现的。)

把这些放在一起,转换将是:

 Derived *x = (Derived *) ((char *) p - offsetof(Derived, base)); 

鉴于支持的不确定性,我只会在实验代码或课堂练习中使用它,除非我从我使用的C实现得到保证,支持这种指针算法。 最好坚持使用底座作为第一个元素,因为那些转换严格符合C.