Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/62.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在C中扩展结构_C_Struct - Fatal编程技术网

在C中扩展结构

在C中扩展结构,c,struct,C,Struct,我最近遇到一位同事的代码如下: typedef struct A { int x; }A; typedef struct B { A a; int d; }B; void fn(){ B *b; ((A*)b)->x = 10; } 他的解释是,由于struct A是struct B的第一个成员,因此B->x将与B->A.x相同,并提供更好的可读性。 这是有道理的,但这被认为是良好的做法吗?这能跨平台工作吗?目前,这在GCC上运行良好。这是一个可怕的想法。一旦有人

我最近遇到一位同事的代码如下:

typedef struct A {
  int x;
}A;

typedef struct B {
  A a;
  int d;
}B;

void fn(){
  B *b;
  ((A*)b)->x = 10;
}
他的解释是,由于
struct A
struct B
的第一个成员,因此
B->x
将与
B->A.x
相同,并提供更好的可读性。

这是有道理的,但这被认为是良好的做法吗?这能跨平台工作吗?目前,这在GCC上运行良好。

这是一个可怕的想法。一旦有人出现并在结构B前面插入另一个字段,您的程序就会崩溃。
b.a.x
有什么不对?

我知道这是怎么回事,但我不认为这是一种好的做法。这取决于每个数据结构的字节在内存中的放置方式。无论何时将一个复杂的数据结构转换为另一个(即结构),这都不是一个好主意,尤其是当两个结构的大小不相同时。

是的,它可以跨平台工作(a),但这并不一定是一个好主意

根据ISO C标准(以下所有引用均来自C11),
6.7.2.1结构和联合说明符/15
,结构的第一个元素前不允许填充

此外,
6.2.7兼容型和复合型规定:

如果两种类型的类型相同,则它们具有兼容的类型

毫无疑问,
A
A-in-B
类型是相同的

这意味着对
A
字段的内存访问在
A
B
两种类型中都是相同的,更合理的
B->A.x
也一样,如果您对将来的可维护性有任何顾虑,那么您可能应该使用它

而且,虽然您通常需要担心严格的类型别名,但我认为这不适用于这里。别名指针是非法的,但该标准有特定的例外情况

6.5表达式/7
说明了其中一些例外情况,但有脚注:

此列表的目的是指定对象可能具有别名或不具有别名的情况

所列的例外情况包括:

  • 与对象的有效类型兼容的类型
  • 我们在这里不必担心的其他一些例外情况;及
  • 一种聚合或联合类型,其成员中包括上述类型之一(递归地包括子聚合或包含的联合的成员)
结合上述结构填充规则,包括以下短语:

指向经过适当转换的结构对象的指针指向其初始成员

似乎表明此示例是专门允许的。这里我们必须记住的核心点是表达式
((A*)b)
的类型是
A*
,而不是
b*
。这使得变量兼容,以实现无限制的别名

这是我对标准相关部分的解读,我之前(b)是错的,但在这种情况下我对此表示怀疑

所以,如果你真的需要它,它可以正常工作,但我会记录代码中的任何约束,非常接近结构,以免将来被咬


(a) 在一般意义上。当然,代码片段:

B *b;
((A*)b)->x = 10;
将是未定义的行为,因为
b
未初始化为合理的行为。但我将假设这只是一个示例代码,用于说明您的问题。如果有人对此表示关注,请将其视为:

B b, *pb = &b;
((A*)pb)->x = 10;


(b) 正如我妻子经常告诉你的那样,几乎没有什么提示:-)

任何绕过类型检查的事情通常都应该避免。 这种攻击依赖于声明的顺序,编译器既不能强制转换也不能强制执行此顺序

它应该跨平台工作,但我认为这不是一个好的实践

如果您确实有深度嵌套的结构(但是,您可能会想知道为什么),那么您应该使用临时局部变量来访问字段:

A deep_a = e->d.c.b.a;
deep_a.x = 10;
deep_a.y = deep_a.x + 72;
e->d.c.b.a = deep_a;
或者,如果您不想复制
a

A* deep_a = &(e->d.c.b.a);
deep_a->x = 10;
deep_a->y = deep_a->x + 72;
这显示了
a
来自何处,不需要强制转换


Java和C#也经常公开“C.b.a”之类的结构,我看不出问题出在哪里。如果你想模拟的是面向对象的行为,那么你应该考虑使用面向对象的语言(如C++),因为“扩展结构”在你提出的方法中不提供封装和运行时多态性(尽管有人可能认为((a)b)类似于一个“动态的CAST”)。.

在这一点上,我将不遗余力地反对:我认为这是一个好主意,在大型的、生产质量代码中非常常见

基本上,这是在C中实现基于继承的面向对象数据结构的最明显和最好的方法。用一个
struct A
实例开始声明
struct B
意味着“B是A的一个子类”。事实上,第一个结构成员保证从结构的开始算起为0字节,这使得它能够安全地工作,在我看来,这是一个非常漂亮的边界

它在基于库的代码中被广泛使用和部署,例如GTK+用户界面工具包和GNOME桌面环境

当然,它要求您“知道自己在做什么”,但在C中实现复杂的类型关系时通常都是这样


在GObject和GTK+的例子中,有大量的支持基础设施和文档可以帮助实现这一点:很难忘记它。这可能意味着创建一个新类不是像C++那样快速完成的事情,但这可能是意料之中的事情,因为C类中没有对本机的支持。

<是的,它会起作用。这是一个核心原则。有关扩展(即继承)的更多示例,请参见此答案“”。

对不起
B *b;
b->parent.parent.g_class->g_type
B *b;
((GTypeInstance*)b)->g_class->g_type
struct person {
  char name[MAX_STRING + 1];
  char address[MAX_STRING + 1];
}

struct item {
  int x;
};

struct accessory {
  int y;
};

/* fixed size memory buffer.
   The Linux kernel is full of embedded structs like this
*/
struct order {
  struct person customer;
  struct item items[MAX_ITEMS];
  struct accessory accessories[MAX_ACCESSORIES];
};

void fn(struct order *the_order){
  memcpy(the_order->customer.name, DEFAULT_NAME, sizeof(DEFAULT_NAME));
}
struct double_order {
  struct order order;
  struct item extra_items[MAX_ITEMS];
  struct accessory extra_accessories[MAX_ACCESSORIES];

};
struct double_order d;
fn((order *)&d);