Доступ к дочерним переменным через структуры более высокого уровня

Если у меня есть эти структуры:

typedef struct { int x; } foo;
typedef struct { foo f; } bar;

Обычно вы должны обращаться к x через b.f.x, но есть ли способ установить это, чтобы вы могли получить доступ к элементу x без ссылки на f?

bar b;
b.x = ...

Моя первая интуиция заключается в том, что вы не можете, так как существует вероятность конфликтов имен, если у двух подструктур есть элемент x, и я не могу понять, что такое ошибка компиляции. Однако, я помню, что работал в некоторых рамках, где это было возможно.

В С++ я работал в среде, где bar существовал, и вы могли получить доступ к своим членам как переменные-члены this->x из другого класса. Я пытаюсь понять, как это можно сделать.

Ответы

Ответ 1

Вы можете использовать C11:

§ 6.7.2.1 - 11

Неименованный элемент, спецификатор типа которого является спецификатором структуры без тега, называется анонимная структура; неназванный член, спецификатор типа которого является спецификатором объединения с no tag не называется анонимным объединением. Члены анонимной структуры или объединения считаются членами структуры или объединения. Это относится рекурсивно, если содержащая структура или объединение также анонимны.

Итак, этот код может работать:

#include <stdio.h>

typedef struct { int x; } foo;
typedef struct { foo; } bar;

int main(void)
{
    bar b;
    b.x = 1;
    printf("%d\n", b.x);
}

Проблема заключается в том, что разные компиляторы не согласны в моих тестах на то, является ли typedef приемлемым как спецификатор структуры без тега. Стандарт указывает:

§ 6.7.8 - 3

В объявлении, спецификатор класса хранения которого typedef, каждый декларатор определяет Идентификатор - это имя typedef, которое обозначает тип, указанный для идентификатора, таким образом описанных в 6.7.6. [...] Объявление typedef не вводит новый тип, только a синоним для указанного типа.

(выделение мое). Но означает ли синоним также, что спецификатор typdef-name заменяется для спецификатора структуры? gcc принимает это, clang не делает.

Конечно, нет способа выразить весь член типа foo этими декларациями, вы жертвуете своим именованным членом f.

Что касается ваших сомнений в столкновении имен, это то, что gcc должно сказать, когда вы помещаете еще один int x внутри bar:

structinherit.c:4:27: error: duplicate member 'x'
 typedef struct { foo; int x; } bar;
                           ^

Чтобы избежать двусмысленности, вы можете просто повторить struct, возможно #define d в качестве макроса, но, конечно, это выглядит немного уродливо:

#include <stdio.h>

typedef struct { int x; } foo;
typedef struct { struct { int x; }; } bar;

int main(void)
{
    bar b;
    b.x = 1;
    printf("%d\n", b.x);
}

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

<opinion> Очень жаль, мне нравится, что синтаксис, принятый gcc, намного лучше, но поскольку формулировка стандарта не делает его явным, чтобы разрешить это, единственная безопасная ставка заключается в том, чтобы предположить, что это запрещено, поэтому clang здесь не виноват... </opinion>

Если вы хотите обратиться к x с помощью b.x или b.f.x, вы можете использовать дополнительный анонимный союз следующим образом:

#include <stdio.h>

typedef struct { int x; } foo;
typedef struct {
    union { struct { int x; }; foo f; };
} bar;

int main(void)
{
    bar b;
    b.f.x = 2;
    b.x = 1;
    printf("%d\n", b.f.x); // <-- guaranteed to print 1
}

Это не вызовет проблемы с псевдонимом из-за

§ 6.5.2.3 - 6

Для упрощения использования союзов существует одна специальная гарантия: если объединение содержит несколько структур, которые имеют общую начальную последовательность (см. ниже), и если объект объединения в настоящее время содержит одну из этих структур, разрешено проверяйте общую начальную часть любого из них в любом месте, где видна декларация завершенного типа соединения. Две структуры имеют общую начальную последовательность, если соответствующие члены имеют совместимые типы (и для бит-полей, одинаковые ширины) для последовательности одного или нескольких начальных элементов

Ответ 2

C: очень не рекомендуется, но выполнимо:

#include <stdio.h>

#define BAR_STRUCT struct { int x; }

typedef BAR_STRUCT bar;

typedef struct {
    union {
        bar b;
        BAR_STRUCT;
    };
} foo;

int main() {
  foo f;
  f.x = 989898;
  printf("%d %d", f.b.x, f.x);

  return 0;
}

Анонимные структуры - это расширенное расширение стандартов до C11.

С++: То же, что и в C, вы можете сделать здесь, но анонимные структуры не являются частью любого стандарта С++, а расширения. Лучше использовать наследование или вообще не использовать этот ярлык.

Конечно, не используйте что-то вроде #define x b.x)).

Ответ 3

В C вы не можете получить доступ к таким членам, как это.

Однако вы можете обращаться к членам анонимной внутренней структуры:

struct bar {
    struct {
        int x;
    }
};

...
struct bar b;
b.x = 1;

В С++ вы используете наследование:

struct foo {
    int x;
};

struct bar: public foo {
};

...
struct bar b;
b.x = 1;

Ответ 4

В C (99 и далее) вы можете получить доступ к общей начальной подпоследовательности членов объединения, даже если они не были последним членом, записанным в 1.

В C11 вы можете иметь анонимных членов профсоюза. Итак:

typedef struct { int x; } foo;
typedef struct {
  union {
    foo f;
    int x;
  };
} bar;

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

Ответ 5

Это невозможно в C. В С++, однако, вы можете использовать наследование, что, вероятно, о чем вы думали.

Ответ 6

В С++ вы можете использовать наследование, а конфликты имен членов - это разбор разрешимости с :: и обработка базовых классов как членов.

struct foo { int x; };
struct bar : foo { };

struct foo1 { int x; };
struct bar1 : foo1 { char const* x; };

bar b;
bar1 b1;
int main()
{
    return b.x + b1.foo1::x;
}

В стандарте C невозможно, однако несколько компиляторов (gcc, clang, tinycc) поддерживают аналогичную функцию расширения (обычно доступного с помощью -fms-extensions (на gcc также с -fplan9-extensions, который является надмножеством -fms-extensions)), что позволяет:

struct foo { int x; };
struct bar { struct foo; };
struct bar b = { 42 }; 
int main()
{
   return b.x;
}

Однако нет разрешения на конфликтующие имена участников с ним, AFAIK.

Ответ 7

В С++ это возможно двумя способами. Первое - использовать наследование. Второй для bar содержит ссылочный элемент с именем x (int &x) и конструкторы, которые инициализируют x для ссылки на f.x.

В C это невозможно.

Ответ 8

Так как стандарт C гарантирует, что перед первым элементом структуры не будет заполнение, перед foo в bar не будет заполнения, и до x в foo. Таким образом, доступ к необработанной памяти к началу bar будет иметь доступ к bar::foo::x.

Вы можете сделать что-то вроде этого:

#include <stdio.h>
#include <stdlib.h>

typedef struct _foo
{
    int x;
} foo;

typedef struct _bar
{
    foo f;
} bar;

int main()
{
    bar b;
    int val = 10;

    // Setting the value:
    memcpy(&b, &val, sizeof(int));
    printf("%d\n", b.f.x);

    b.f.x = 100;


    // Reading the value:
    memcpy(&val, &b, sizeof(int));
    printf("%d\n", val);
    return 0;
}

Как отмечали другие, С++ предлагает более элегантный способ сделать это через наследование.