Приведение одного указателя структуры к другому - C - PullRequest
27 голосов
/ 22 сентября 2010

Пожалуйста, рассмотрите следующий код.

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. Есть ли какие-либо проблемы в выполнении подобных вещей?

Есть мысли!

Ответы [ 3 ]

32 голосов
/ 22 сентября 2010

Это хорошо и является довольно распространенным методом для реализации «объектной ориентации» в 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, он будет автоматически подхвачен дочерними элементами, и вам не нужно будет вручную проверять синхронизацию всех структур.

17 голосов
/ 22 сентября 2010

Чтобы добавить к ответу Дина, вот кое-что о преобразованиях указателя в целом. Я забыл, что термин для этого, но указатель на приведение указателя не выполняет преобразование (так же, как 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 *
                        +---+---+---+---+
9 голосов
/ 22 сентября 2010

Использование отдельных структур нарушает правило строгого псевдонима и является неопределенным поведением: http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...