Выделение одного указателя структуры другому - 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
- разные структуры.
- Есть ли какие-либо проблемы при создании таких вещей?
Любые мысли!
Ответы
Ответ 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 *
+---+---+---+---+
Ответ 3
Использование отдельных структур нарушает строгое правило псевдонимов и поведение undefined: http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html
Использование встроенной структуры, как в предыдущем примере Дин, отлично.