Гарантируется ли многоуровневое «структурное наследование» везде? - PullRequest
3 голосов
/ 06 апреля 2020

Я знаю, что в C первый член структуры гарантированно не будет заполнен до него. Таким образом, &mystruct == &mystruct.firstmember всегда истинно.

Это позволяет использовать технику "struct Наследование", , как описано в этом вопросе :

typedef struct
{
    // base members

} Base;

typedef struct
{
    Base base;

    // derived members

} Derived;

// ... later
Base* object = (Base*) malloc(sizeof()); // This is legal

Однако я бы хотел чтобы убедиться, что это на самом деле безопасно работает с неограниченным уровнем «наследования». Например:

typedef struct
{
    // members

} A;

typedef struct
{
    A base;

    // members

} B;

typedef struct 
{
    B base;

    // members
} C;

Гарантируется ли работа всех следующих видов использования?

A* a = (A*) malloc(sizeof(B));
A* a = (A*) malloc(sizeof(C));
B* b = (B*) malloc(sizeof(C));
C* c = malloc(sizeof(C));

// ... use and access members through the pointers

РЕДАКТИРОВАТЬ:

Позвольте мне уточнить, что я ' Я спрашиваю. Гарантируется ли следующее использование «многоуровневого наследования» по стандарту C?

C* c = malloc(sizeof(C));
// ... initialize fields in c

A* a = (A*) c;
// ... use A fields in a

B* b = (B*) a;
// ... use B fields in b

B* b = (B*) c;
// ... use B fields in b

c = (C*) a;
// ... go back to using C fields in c

Ответы [ 2 ]

4 голосов
/ 11 апреля 2020

То, что тип «многоуровневого наследования», который вы описываете, должно работать, следует из тех же принципов - объясненных в других вопросах и ответах, на которые вы ссылались - что делает этот тип наследования работающим вообще. В частности, стандарт явно предусматривает, что приведение адресов структур и их начальных членов между применимыми типами дает желаемый эффект:

Указатель на объект структуры, соответствующим образом преобразованный, указывает на его начальный член [...] и наоборот.

(пункт 6.7.2.1/15)

Итак, рассмотрим это объявление относительно предоставленных определений структуры:

C c;

В цитируемом положении указано, что оба значения &c == (C *) &c.base и (B *) &c == &c.base имеют значение true.

Но c.base - это B, поэтому в положении также указано, что (A *) &c.base == &c.base.base и &c.base == (B *) &c.base.base оба true.

Так как (B *) &c == &c.base равно true и &c.base == (B *) &c.base.base оба имеют значение true, из этого следует, что (B *) &c == (B *) &c.base.base также имеет значение true.

Приведение обеих сторон к A * или C * затем выдает также равенства (A *) &c == &c.base.base и &c == (C *) &c.base.base.

Это рассуждение может быть расширено до произвольной глубины вложенности.

Можно немного рассуждать о динамически распределенных структурах относительно vis s правило строгого псевдонима, но нет никаких оснований полагать, что в этом случае предполагается, что оно будет работать по-другому, и до тех пор, пока кто-то сначала обращается к динамически выделенному пространству через lvalue наиболее определенного типа c (C в в этом примере), я не вижу сценария, который поддерживает другую интерпретацию стандарта для случая динамического размещения c, чем в других случаях. На практике я не ожидаю, что начальный доступ через наиболее конкретный тип c фактически потребуется какой-либо реализацией.

2 голосов
/ 11 апреля 2020

Стандарт ISO C, необходимый для работы, является следующей ситуацией:

union U {
  struct X x;
  struct Y y;
  struct Z z;
  /* ... */
};

Если структуры разделяют некоторую общую начальную последовательность элементов, то к этой начальной последовательности можно получить доступ через любого из элементов. , Например:

 struct X {
   /* common members, same as in Y and Z: */
   int type;
   unsigned flags;

   /* different members */
 };

Если все структуры имеют type и flags в одном и том же порядке и одинаковых типов, то это необходимо для работы:

union U u;
u.x.type = 42;  /* store through x.type */
foo(u.y.type);  /* access through y.type */

Другие хаки этого типа не «благословлены» ISO C.

Ситуация там немного другая. Вопрос в том, можем ли мы, если у ведущего элемента структуры, преобразовать указатель на структуру в тип этого члена, а затем использовать его. Простейший случай выглядит примерно так:

struct S {
  int m;
};

Учитывая объект struct S s, мы можем взять адрес m, используя &s.m, получив указатель int *. Эквивалентно, мы можем получить тот же указатель, используя (int *) &s.

ISO C требует, чтобы структура имела тот же адрес, что и ее первый член; указатель на структуру и указатель на первый член имеют другой тип, но указывают на один и тот же адрес, и мы можем преобразовать их между собой.

Это не ограничено уровнями вложенности. Учитывая a этого типа:

struct A {
  struct B {
    struct C {
       int m;
    } c;
  } b
};

адрес &a.b.c.m остается таким же, как адрес &a. Указатель &a.b.c.m совпадает с (int *) &a.

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