将一个结构指针转换为另一个 – 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*
。
- 我想知道这是怎么可能的,因为
cons_object
和object
是不同的结构。 - 做这样的事情有什么问题吗?
有什么想法吗!
这很好,是在C中实现“面向对象”的一种相当常见的技术。因为struct
的内存布局在C中定义良好,只要两个对象共享相同的布局,就可以安全地在两者之间转换指针。他们。 也就是说, type
成员的偏移量在object
结构中与cons_object
结构中的cons_object
。
在这种情况下, type
成员告诉API object
是cons_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
的指针,则编译器知道存在具有特定偏移量的字段,该偏移量称为type
为enum 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的最后一个例子中使用嵌入式结构很好。