C 扩展结构
我在SO中阅读了上面的代码片段C 扩展结构,c,struct,C,Struct,我在SO中阅读了上面的代码片段((A*)b)->x不是好的编程风格。b->A.x是好的编程风格。因为任何人在结构b中的语句“A;”之前添加了一些内容,所以它将不起作用。我不明白为什么?我也试过了。有什么建议吗?该技巧用于模拟C中的继承。它可以将地址A或B传递给期望指针指向A的函数 这是因为C保证在结构的第一个成员之前没有填充。因此,如果A是B的第一个成员,则B开头的内存布局始终与A相同 typedef struct A { int x; }A; typedef struct B {
((A*)b)->x
不是好的编程风格。b->A.x
是好的编程风格。因为任何人在结构b中的语句“A;”之前添加了一些内容,所以它将不起作用。我不明白为什么?我也试过了。有什么建议吗?该技巧用于模拟C中的继承。它可以将地址A或B传递给期望指针指向A的函数
这是因为C保证在结构的第一个成员之前没有填充。因此,如果A是B的第一个成员,则B开头的内存布局始终与A相同
typedef struct A
{
int x;
}A;
typedef struct B
{
A a;
int d;
}B;
void fn()
{
B *b;
((A*)b)->x = 10;
}
如果您要更改B声明:
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
这将不再起作用,因为
(A*)&b
将返回b.d
,而不是b.A
,该技巧用于模拟C中的继承。它可以将地址A或b传递给期望指针指向A的函数
这是因为C保证在结构的第一个成员之前没有填充。因此,如果A是B的第一个成员,则B开头的内存布局始终与A相同
typedef struct A
{
int x;
}A;
typedef struct B
{
A a;
int d;
}B;
void fn()
{
B *b;
((A*)b)->x = 10;
}
如果您要更改B声明:
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
这将不再有效,因为(A*)&b
将返回b.d
的地址,而不是b.A
您这里拥有的是“穷人的遗产”。与C++中的真正继承一样,它用于定义一种类型,它包含对象的共同特征(数据、函数指针),这些对象实际上可以承载比仅仅是公共子集更多的信息。p>
该技术广泛应用于GhostScript中,例如打印机驱动程序携带一些公共信息和最重要的特殊信息来控制特定的打印机型号
这里使用的C语言机制是,结构本质上是其数据在内存中的串联,按照成员声明的顺序。该顺序对于在强制转换后获得正确的访问权非常重要。
您的B的内存布局是--intx--intd--
。没有存储其他信息。A*
指向第一个元素x;aB*
也是如此。你可以有一个c结构
typedef struct B
{
int d;
A a;
}B;
其布局将是|--intx----intd----float f---
。有趣的是,您可以将A*pa
传递给一个函数,该函数不知何故知道pa实际上指向C,并将其“向下”转换为((C*)pa)->f
(C*)pa
不会更改pa的值,只是告诉编译器它指向什么(由程序员负责)。关于对象中实际隐藏的类型的知识通常被编码在enum/int数据成员中,该成员在创建对象时被手动设置为一个神奇的类型指示值。这里您拥有的是一个“穷人的继承”。与C++中的真正继承一样,它用于定义一种类型,它包含对象的共同特征(数据、函数指针),这些对象实际上可以承载比仅仅是公共子集更多的信息。p>
该技术广泛应用于GhostScript中,例如打印机驱动程序携带一些公共信息和最重要的特殊信息来控制特定的打印机型号
这里使用的C语言机制是,结构本质上是其数据在内存中的串联,按照成员声明的顺序。该顺序对于在强制转换后获得正确的访问权非常重要。
您的B的内存布局是--intx--intd--
。没有存储其他信息。A*
指向第一个元素x;aB*
也是如此。你可以有一个c结构
typedef struct B
{
int d;
A a;
}B;
其布局将是
|--intx----intd----float f---
。有趣的是,您可以将A*pa
传递给一个函数,该函数不知何故知道pa实际上指向C,并将其“向下”转换为((C*)pa)->f
(C*)pa
不会更改pa的值,只是告诉编译器它指向什么(由程序员负责)。关于对象中实际隐藏的类型的知识通常被编码在enum/int数据成员中,该成员在创建对象时被手动设置为神奇的类型指示值。为什么b
会被强制转换为a*
?这两种类型似乎不相关,除了B
具有a
类型的成员。据我所知,赋值((A*)b)->x=10将取决于特定的内存布局。b->A.x很容易理解。但另一种形式对我来说并不容易理解。如果我没有弄错的话,这两个作业首先在结构上做了不同的事情,而且最多只是偶然得到相同的结果。是的,两个作业做的事情是一样的。我的疑问是为什么这句话是一个;应该是结构b的第一个语句,以便使用((A*)b)->x
获得所需的结果。为什么b
要转换为A*
?这两种类型似乎不相关,除了B
具有a
类型的成员。据我所知,赋值((A*)b)->x=10将取决于特定的内存布局。b->A.x很容易理解。但另一种形式对我来说并不容易理解。如果我没有弄错的话,这两个作业首先在结构上做了不同的事情,而且最多只是偶然得到相同的结果。是的,两个作业做的事情是一样的。我的疑问是为什么这句话是一个;应该是结构b的第一个语句,以便使用((A*)b)->x
获得所需的结果。非常有趣,我完全不知道在C中实现继承的可能性。非常感谢!警告一句:这仍然是一个骗局,很容易变得难以维持。如果可能的话,我真的建议使用支持继承的语言作为核心特性