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

时间:2010-09-22 04:08:28

标签: c struct casting

请考虑以下代码。

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*的类型。

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

3 个答案:

答案 0 :(得分:32)

这很好,是在C中实现“面向对象”的一种相当常见的技术。因为struct的内存布局在C中定义良好,只要两个对象共享相同的布局然后你可以安全地在它们之间转换指针。也就是说,type结构中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;

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

答案 1 :(得分:17)

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

如,
如果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的指针。如果您要访问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 *
                        +---+---+---+---+

答案 2 :(得分:9)

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

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