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

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

Есть предположения!

Это хорошо и является довольно распространенным методом реализации «объектной ориентации» в C. Поскольку макет памяти struct s хорошо определен в 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 } 

Чаще всего я представляю реализации, где «родительский» class определяется как первый член «дочернего» classа, например:

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

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

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

например,
Если ptr является указателем на object , компилятор знает, что существует поле с определенным смещением с именем type типа enum type . С другой стороны, если ptr cons_object указателю на другой тип 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 его указателю на объект cons_object . Если бы вы получили доступ к полю car , это точно так же, как любой обычный доступ к памяти. Он имеет некоторое смещение от начала структуры. В этом случае то, что находится в этой ячейке памяти, неизвестно, но это неважно. Для доступа к полю требуется только смещение и информация содержится в определении типа.

Указатель на int указывает на блок памяти:

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

cons_object указатель 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

Использование встроенной структуры, как в последнем примере Дин, прекрасно.

  • Как определить структуру typedef, содержащую указатели на себя?
  • Несовместимое неявное объявление встроенной функции 'malloc'
  • Передача структуры в функцию
  • Вызывать функцию golang struct дает «не может ссылаться на невыполненное поле или метод»
  • Почему размер sizeof для структуры не равен сумме sizeof каждого члена?
  • Как проверить, имеет ли переменная определенный тип (сравнивают два типа) в C?
  • Почему C имеет различие между -> и.?
  • Почему компиляторы C не могут изменять элементы структуры для исключения выравнивания?
  • как устанавливать и получать поля в структурах Golang?
  • Класс смешивания и структура
  • Как определить структуру в Matlab
  • Давайте будем гением компьютера.