将一个结构指针转换为另一个 – C

请考虑以下代码。

enum type {CONS, ATOM, FUNC, LAMBDA}; typedef struct{ enum type type; } object; typedef struct { enum type type; object *car; object *cdr; } cons_object; object *cons (object *first, object *second) { cons_object *ptr = (cons_object *) malloc (sizeof (cons_object)); ptr->type = CONS; ptr->car = first; ptr->cdr = second; return (object *) ptr; } 

cons函数中,变量ptr的类型为cons_object* 。 但在返回值中,它将转换为object*类型object*

  1. 我想知道这是怎么可能的,因为cons_objectobject是不同的结构。
  2. 做这样的事情有什么问题吗?

有什么想法吗!

这很好,是在C中实现“面向对象”的一种相当常见的技术。因为struct的内存布局在C中定义良好,只要两个对象共享相同的布局,就可以安全地在两者之间转换指针。他们。 也就是说, type成员的偏移量在object结构中与cons_object结构中的cons_object

在这种情况下, type成员告诉API objectcons_object还是foo_object或其他类型的对象,因此您可能会看到如下所示的内容:

 void traverse(object *obj) { if (obj->type == CONS) { cons_object *cons = (cons_object *)obj; traverse(cons->car); traverse(cons->cdr); } else if (obj->type == FOO) { foo_object *foo = (foo_object *)obj; traverse_foo(foo); } else ... etc } 

更常见的是,我似乎将“父”类定义为“子”类的第一个成员,如下所示:

 typedef struct { enum type type; } object; typedef struct { object parent; object *car; object *cdr; } cons_object; 

这在很大程度上是相同的,除了你有一个强烈的保证,孩子“类”的记忆布局将与父母相同。 也就是说,如果你向’base’ object添加一个成员,它将自动被孩子们拾取,你不必手动确保所有结构都是同步的。

要添加到Dean的答案,这里有一些关于指针转换的内容。 我忘记了这个术语是什么,但是指向指针强制转换的指针不执行任何转换(与浮点数相同的是int)。 它只是对它们指向的位的重新解释(所有这些都是为了编译器的好处)。 “非破坏性转换”我认为是。 数据不会改变,只是编译器如何解释所指向的内容。

例如,
如果ptr是指向object的指针,则编译器知道存在具有特定偏移量的字段,该偏移量称为typeenum type 。 另一方面,如果ptr被转换为指向不同类型的指针cons_object ,它将再次知道如何以类似的方式访问cons_object各个字段,每个字段具有它们自己的偏移量。

为了说明cons_object的内存布局:

  +---+---+---+---+ cons_object *ptr -> | t | y | p | e | enum type +---+---+---+---+ | c | a | r | | object * +---+---+---+---+ | c | d | r | | object * +---+---+---+---+ 

type字段的偏移量为0, car为4, cdr为8.要访问car字段,所有编译器需要做的是将4添加到结构的指针。

如果指针被强制转换为指向object的指针:

  +---+---+---+---+ ((object *)ptr) -> | t | y | p | e | enum type +---+---+---+---+ | c | a | r | | +---+---+---+---+ | c | d | r | | +---+---+---+---+ 

所有编译器需要知道的是有一个名为type的字段,偏移量为0.内存中的内容是什么。

指针甚至不必相关。 您可以拥有一个指向int的指针并将其cons_object转换为指向cons_object的指针。 如果您要访问car领域,它就像任何普通的内存访问一样。 它与结构的开头有一定的偏差。 在这种情况下,该内存位置的内容是未知的,但这并不重要。 要访问字段,只需要偏移量,并在类型的定义中找到该信息。

指向int的指针指向内存块:

  +---+---+---+---+ int *ptr -> | i | n | t | | int +---+---+---+---+ 

转向cons_object指针:

  +---+---+---+---+ ((cons_object *)ptr) -> | i | n | t | | enum type +---+---+---+---+ | X | X | X | X | object * +---+---+---+---+ | X | X | X | X | object * +---+---+---+---+ 

使用单独的结构违反了严格的别名规则,并且是未定义的行为: http : //cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html

在Dean的最后一个例子中使用嵌入式结构很好。