Подтвердить что ты не робот

Выделение одного указателя структуры другому - 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*.

  • Мне интересно, как это возможно, потому что cons_object и object - разные структуры.
  • Есть ли какие-либо проблемы при создании таких вещей?

Любые мысли!

4b9b3361

Ответ 1

Это хорошо и является довольно распространенным методом реализации "объектной ориентации" в C. Поскольку макет памяти struct хорошо определен в C, если два объекта имеют один и тот же макет, вы можете безопасно литые указатели между ними. То есть смещение члена type является тем же самым в структуре 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;

Это работает в основном таким же образом, за исключением того, что у вас есть сильный gaurantee, что макет памяти дочерних "классов" будет таким же, как и у родителей. То есть, если вы добавите элемент в "базу" object, он автоматически будет загружен детьми, и вам не придется вручную проверять синхронизацию всех структур.

Ответ 2

Чтобы добавить в ответ Дин, вот что-то о конверсиях указателей в целом. Я забыл, для чего этот термин, но указатель на листинг указателя не выполняет преобразование (точно так же, как int to float is). Это просто переинтерпретация бит, на который они указывают (все для выгоды компилятора). "Неразрушающая конверсия" Я так думаю. Данные не изменяются, только то, как компилятор интерпретирует то, на что указывает.

например.,
Если 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. Чтобы получить доступ к полю автомобиля, все, что нужно сделать компилятору, это добавить 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 *
                        +---+---+---+---+