typedef struct A
{
int x;
}A;
typedef struct B
{
A a;
int d;
}B;
void fn()
{
B *b;
((A*)b)->x = 10;
}
我在SO中阅读了上面的代码片段。 ((A*)b)->x
不是很好的编程风格。b->a.x
是很好的编程风格。因为任何人在声明之前添加了一些内容" A a;"在结构b中,它将无法工作。我不明白为什么?我也尝试过了。有什么建议吗?
答案 0 :(得分:4)
该技巧用于模拟C中的继承。它可以将地址A或B传递给期望指向A的函数。
这是有效的,因为C保证在struct的第一个成员之前没有填充。因此,如果A是B的第一个成员,则B开头的内存布局始终与A相同。
int doStuff(A * a) {
return a->x + 1;
}
...
B b;
doStuff((A*)&b); // Will work because b and b.a have the same start address
如果您要更改B声明:
typedef struct B
{
int d;
A a;
}B;
这将不再有效,因为(A*)&b
将返回b.d
的地址,而不是b.a
。
答案 1 :(得分:1)
你在这里所拥有的是一个穷人的遗产"。与C ++中的真正继承一样,它用于定义包含对象的公共特征(数据,函数指针)的类型,这些对象实际上可以携带比公共子集更多的信息。
该技术广泛用于例如GhostScript,其中打印机驱动程序包含一些常用信息和最重要的特殊信息,以控制特定的打印机型号。
这里使用的C语言机制是结构本质上是其内存中数据的串联,按成员声明的顺序。该顺序对于在转换后立即获取访问权限非常重要。
B的内存布局为|---int x---|---int d ---|
。没有存储其他信息。 A *
指向第一个元素x; B *
也是如此。你可以有一个struct c
struct C
{
B b;
float f;
};
其布局为|---int x---|---int d ---|-----float f---|
。有趣的是,你可以将一个A *pa
传递给一个函数,该函数以某种方式知道pa实际指向一个C并且向下投射" down":((C *)pa)->f
。 (C *)pa
不会改变pa的值,只是告诉编译器它指向的是什么(由程序员负责)。有关实际类型隐藏在对象中的知识通常编码在enum / int数据成员中,该成员在创建对象时手动设置为魔术类型指示值。