Приведение одного указателя структуры к другому - 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
разные структуры. - Есть ли какие-то проблемы в таких вещах?
Какие-нибудь мысли!
3 ответа
Это хорошо и является довольно распространенным методом для реализации "объектно-ориентированной" на 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
}
Чаще всего мне кажутся реализации, где родительский класс определяется как первый член дочернего класса, например так:
typedef struct {
enum type type;
} object;
typedef struct {
object parent;
object *car;
object *cdr;
} cons_object;
Это работает в основном так же, за исключением того, что у вас есть надежная гарантия того, что расположение памяти дочерних "классов" будет таким же, как у родителей. То есть, если вы добавите участника в "базу" object
, он будет автоматически выбран детьми, и вам не нужно будет вручную проверять синхронизацию всех структур.
Чтобы добавить к ответу Дина, вот кое-что о преобразованиях указателя в целом. Я забыл, что термин для этого, но указатель на приведение указателя не выполняет преобразование (так же, как int в float). Это просто переосмысление битов, на которые они указывают (все для выгоды компилятора). "Неразрушающее преобразование", я думаю, это было. Данные не меняются, только то, как компилятор интерпретирует то, на что указывают.
например,
Если 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 *
+---+---+---+---+
Использование отдельных структур нарушает строгое правило наложения имен и является неопределенным поведением: http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html
Использование встроенной структуры, как в последнем примере Дина, хорошо.