我尝试使用以下代码在C中模拟C的多态性:
#include<stdio.h>
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时,我必须进行不兼容的指针分配.为了使其正常工作,“派生”中的基本成员也必须是第一个成员.如何避免两者?
解决方法:
不需要不兼容的指针分配.根据C 2011 [草稿N1570] 6.7.2.1 15,“指向经过适当转换的结构对象的指针指向其初始成员(或者,如果该成员是位字段,则指向它所驻留的单元),并且因此,当Derived的第一个元素是基本对象时,指向Derived的指针可以转换为指向Base的指针.并且,可以将指向作为派生对象中的第一个成员的Base的指针转换为指向派生对象的指针.
所以这是合法的.编译器可能会警告您它只是为了安全起见,因为这很罕见.使用显式强制转换可以避免这种情况:
p = (Base *) &d;
或者,您可以直接获取基地的地址:
p = &d.base;
但是,从基准返回到派生仍然需要强制转换:
Derived *x = (Derived *) p;
至于将基地放在第一位成员之外的其他地方,那么您将处于更不稳定的境地.派生到基本方向仍然很容易. p =& d.base很好.换句话说,这可能与合法的C尽可能接近:
>获取指向基数的字符类型指针:char * c =(char *)p.在C语言中,将指向对象的任何指针转换为指向字符类型的指针是合法的,这为您提供了指向该对象的第一个字节的指针.
>从Derived的开头减去Base成员的字节数:c-= offsetof(Derived,base). (这需要包含< stddef.h>.)由于c指向Derived中Base的第一个字节,因此它指向Derived中的一个字节,并且您大部分(参见下文)被允许将Derived视为字节数组. ,因此我们可以减去以回到开头. offsetof宏为此提供了字节数.
>将字符指针转换为Derived的指针:Derived * x =(Derived *)c.在这里,我们处境不稳.如果将指针从“派生”转换为字符指针,则可以立即将其转换回.但是我们没有以这种方式获得此指针.它指向同一个字节,但是对我来说C标准是否支持此字节尚不清楚. (实际上,上面的步骤2有一个类似的问题;一个对象可以解释为字符数组,但是我们从不确定C标准实际上是否支持的方向来解决这个问题.)
将它们放在一起,转换将是:
Derived *x = (Derived *) ((char *) p - offsetof(Derived, base));
考虑到支持的不确定性,除非在C实现中保证我正在使用支持此指针算法的功能,否则我只能在实验代码中或在课堂练习中使用它.最好坚持使用base作为第一个元素,因为这些转换严格符合C.