Инициализировать статическую константную переменную блочной области с указателем на составной литерал?
Следующий код отклонен 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;
}