Выделение одного указателя структуры другому – 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
– разные структуры. - Есть ли какие-то проблемы при создании подобных вещей?
Есть предположения!
- Имеет ли смысл определять структуру с элементом ссылочного типа?
- Variable Sized Struct C ++
- Что нужно переопределить в структуре, чтобы обеспечить правильное функционирование равенства?
- Как правильно назначить новое строковое значение?
- Почему я не могу определить конструктор по умолчанию для структуры в .NET?
- Почему изменчивые структуры «злые»?
- Инициализация структуры языка программирования C / C ++?
- Почему открытые поля быстрее, чем свойства?
- Почему размер sizeof для структуры не равен сумме sizeof каждого члена?
- Почему 16 байтов рекомендуемый размер для структуры в C #?
- Почему C ++ запрещает анонимные структуры?
- самореферентное определение структуры?
- Почему «typdef struct {struct S * s; } S; ", содержащий указатель на компиляцию того же типа?
Это хорошо и является довольно распространенным методом реализации «объектной ориентации» в 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
Использование встроенной структуры, как в последнем примере Дин, прекрасно.