Инициализировать статическую константную переменную блочной области с указателем на составной литерал?

Следующий код отклонен GCC и Clang (ссылка godbolt):

      struct thing;

typedef enum {
  THING_TYPE_A,
  THING_TYPE_B,
} thing_type_t;

typedef struct thing_a {
  int i;
} thing_a_t;

typedef struct thing_b {
  struct thing const *t;
} thing_b_t;

typedef struct thing {
  thing_type_t type;
  union {
    thing_a_t a;
    thing_b_t b;
  } t;
} thing_t;

thing_t const *get_thing(void) {
  static const thing_t s_thing = {
    .type = THING_TYPE_B, 
    .t = {
      .b = { 
        .t = &(thing_t) { .type = THING_TYPE_A, .t = { .a = { .i = 234 } } } 
      }
    },   
  };

  return &s_thing;
}

Страница cppreference о составных литералах говорит:

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

Я считаю, что это объясняет ошибку компиляции; аноним, чей адрес используется для инициализацииs_thing.t.b.tимеет автоматическую длительность хранения и поэтому не является константой времени компиляции. Если он перемещен в область действия файла, его принимают и Clang, и GCC. (По этому вопросу SO ведется дополнительная дискуссия )

Похоже, C23 расширит это, разрешивconstexprуказываться внутри составных буквальных круглых скобок, что является долгожданным улучшением!

Между тем, есть ли какой-либо способ добиться такой декларации, какs_thing(то есть инициализация статической константной структуры, содержащей указатель на другую константную переменную) в версиях до C23 в области блока без необходимости явного объявления анонимной переменной.thing_tкак отдельную переменную?

2 ответа

Между тем, есть ли какой-либо способ добиться такой декларации, какs_thing(то есть инициализация статической константной структуры, содержащей указатель на другую константную переменную) в версиях до C23 в области блока без необходимости явного объявления анонимной переменной.thing_tкак отдельную переменную?

Нет, вы фактически исключили все возможности.

  • Инициализатор объекта со статической длительностью хранения может содержать только константные выражения.

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

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

И не думаю, что это другое в С23 поможет. Да, вы можете использоватьconstexprв объявлении составного литерала, чтобы получить «составную литеральную константу» структурного типа, но, насколько я могу судить, это не обеспечивает статическую продолжительность хранения указанного объекта. А если объект не имеет статического срока хранения, то его адрес не является адресной константой.

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

Вы правы относительно причины ошибки: составные литералы в области блока всегда имеют автоматическую продолжительность хранения, даже если вы пытаетесь использовать их для инициализации объекта.

Если главная мотивация – не допуститьs_thingчтобы быть видимым под этим именем как глобальный объект, вы можете определить его какstaticпеременная области файла в исходном файле сама по себе вместе сget_thingвернуть его адрес.

вещь.ч:

      struct thing;

typedef enum {
  THING_TYPE_A,
  THING_TYPE_B,
} thing_type_t;

typedef struct thing_a {
  int i;
} thing_a_t;

typedef struct thing_b {
  struct thing const *t;
} thing_b_t;

typedef struct thing {
  thing_type_t type;
  union {
    thing_a_t a;
    thing_b_t b;
  } t;
} thing_t;

thing_t const *get_thing(void);

вещь.с:

      static const thing_t s_thing = {
    .type = THING_TYPE_B, 
    .t = {
      .b = { 
        .t = &(thing_t) { .type = THING_TYPE_A, .t = { .a = { .i = 234 } } } 
      }
    },   
};

thing_t const *get_thing(void)
{
  return &s_thing;
}

Если вы действительно хотите иметь его в области файла, ваш единственный вариант, как вы сказали, — использовать отдельный именованный статический объект вместо составного литерала:

      thing_t const *get_thing(void) {
  static const thing_t tmp_thing = 
          { .type = THING_TYPE_A, .t = { .a = { .i = 234 } } };
  static const thing_t s_thing = {
    .type = THING_TYPE_B,
    .t = {
      .b = {
        .t = &tmp_thing
      }
    },
  };

  return &s_thing;
}
Другие вопросы по тегам