Статическая инициализация структуры в c99
Я столкнулся с странным поведением при использовании сложных литералов для статической инициализации структуры в GCC
в режимах c99/gnu99
.
По-видимому, это нормально:
struct Test
{
int a;
};
static struct Test tt = {1}; /* 1 */
Однако это не так:
static struct Test tt = (struct Test) {1}; /* 2 */
Это вызывает следующую ошибку:
Элемент инициализации не является константой
Также это не помогает:
static struct Test tt = (const struct Test) {1}; /* 3 */
Я понимаю, что значение инициализации для статической структуры должно быть константой времени компиляции. Но я не понимаю, почему это самое простое выражение инициализатора больше не считается постоянным? Является ли это стандартом?
Причина, по которой я спрашиваю, заключается в том, что я столкнулся с некоторым унаследованным кодом, написанным в GCC в режиме gnu90, который использовал такую составную литералную конструкцию для статической инициализации структуры (2). По-видимому, это было расширение GNU в то время, которое позднее было принято C99.
И теперь это приводит к тому, что код, который успешно скомпилирован с помощью GNU90
, не может быть скомпилирован ни с C99
, ни даже с GNU99
.
Зачем мне это делать?
Ответы
Ответ 1
Язык C зависит от точного определения того, что является постоянным выражением. Просто потому, что что-то выглядит "известным во время компиляции", не означает, что оно удовлетворяет формальному определению постоянного выражения.
Язык C не определяет постоянные выражения нескалярных типов. Он позволяет реализациям вводить собственные виды постоянных выражений, но тот, который определен стандартом, ограничивается только скалярными типами.
Другими словами, язык C не определяет понятие постоянного выражения для вашего типа struct Test
. Любое значение struct Test
не является константой. Составной литерал (struct Test) {1}
не является константой (и не является строковым литералом), и по этой причине он не может использоваться в качестве инициализатора для объектов со статической продолжительностью хранения. Добавление классификатора const
к нему ничего не изменит, поскольку в C const
определитель не имеет никакого отношения к понятию постоянного выражения. Это никогда не будет иметь никакого значения в таких контекстах.
Обратите внимание, что ваш первый вариант вообще не включает составной литерал. Он использует исходный синтаксис { ... }
с постоянными выражениями внутри. Это явно разрешено для объектов со статической продолжительностью хранения.
Итак, в самом ограничительном смысле инициализация составным литералом является незаконной, а инициализация с обычным инициализатором { ... }
прекрасна. Некоторые компиляторы могут принимать инициализацию составного литерала как расширение. (Расширяя понятие постоянного выражения или принимая другой путь расширения. Обратитесь к документации компилятора, чтобы выяснить, почему он компилируется.)
Ответ 2
Этот является/является ошибкой gcc (HT to cremno), в отчете об ошибке говорится:
Я считаю, что мы должны просто разрешить инициализацию объектов со статическими длительность хранения с составными литералами даже в gnu99/gnu11. [...] (Но предупреждайте с -pedantic.)
Из документа gcc на сложных литералах видно, что инициализация объектов со статической продолжительностью хранения должна поддерживаться как расширение:
Как расширение GNU, GCC позволяет инициализировать объекты со статическими длительность хранения по составным литералам (что невозможно в ИСО C99, поскольку инициализатор не является константой).
Это исправлено в gcc 5.2
. Таким образом, в gcc 5.2
вы получите это предупреждение только при использовании флага -pedantic
видеть его в прямом эфире, который не жалуется без -pedantic
.
Использование -pedantic
означает, что gcc должен предоставлять диагностику по мере необходимости:
чтобы получить всю диагностику, требуемую стандартом, вы должны также укажите -pedantic (или -pedantic-errors, если вы хотите, чтобы они были ошибки, а не предупреждения)
Компонный литерал не является константным выражением, которое рассматривается в стандартном разделе проекта C99 6.6
Константные выражения, из раздела 6.7.8
Инициализация видно, что:
Все выражения в инициализаторе для объекта с длительностью статического хранения должны быть константные выражения или строковые литералы.
gcc разрешено принимать другие формы константных выражений как расширение, из раздела 6.6
:
Реализация может принимать другие формы постоянных выражений.
интересно отметить, что кланг не жалуется на это, используя -pedantic
Ответ 3
Интересно, что clang
не жалуется на этот код, даже с флагом -pedantic-errors
.
Это, безусловно, относится к C11 §6.7.9/p4 Инициализация (акцент мой идет вперед)
Все выражения в инициализаторе для объекта, который имеет статические или длительность хранения потоков должна быть константными выражениями или строкой литералы.
Другим подпунктом для изучения является §6.5.2.5/p5 Составные литералы:
Значение составного литерала - это значение неназванного объектаинициализируется списком инициализаторов. Если составной литерал вне тела функции объект имеет статическое хранилище Продолжительность; в противном случае он имеет автоматическую продолжительность хранения, связанную с закрывающий блок.
и (для полноты) §6.5.2.5/p4:
В любом случае результатом является lvalue.
но это не означает, что такой неназванный объект можно рассматривать как постоянное выражение. В выражении 6,6 константы говорится, inter alia:
2) Постоянное выражение может быть оценено во время чем время выполнения, и, соответственно, может использоваться в любом месте, где постоянная может быть.
3) Константные выражения не должны содержать присвоение, приращение, декремент, функция-вызов или запятая, за исключением случаев, когда они содержащихся в подвыражении, которое не оценивается.
10) Реализация может принимать другие формы постоянных выражений.
Нет явных упоминаний о составных литералах, хотя, таким образом, я бы это интерпретировал, они недействительны как постоянные выражения в строго соответствующей программе (таким образом, я бы сказал, что clang
имеет ошибку).
В разделе J.2 Undefined поведение (информативное) также разъясняется, что:
Постоянное выражение в инициализаторе не является или не оценивает to, одно из следующего: выражение арифметической константы, null константу указателя, константу адреса или константу адреса для полный тип объекта плюс или минус целочисленное постоянное выражение (6.6).
Опять же, не упоминается о составных литералах.
Тем не менее, в туннеле есть свет. Другой способ, который полностью дезинфицирован, - передать такой неназванный объект, как постоянный адрес. Стандартные состояния в §6.6/p9, которые:
Константа адреса - это нулевой указатель, указатель на lvalue обозначение объекта статической продолжительности хранения или указатель на обозначение функции; он должен быть явно создан с использованием унарного &
оператора или целочисленной константы, отличной от типа указателя, или неявно использование выражения типа массива или функции. array-subscript []
и члены-члены .
и ->
, адрес &
и косвенные *
унарные операторы, а указатели-указатели могут использоваться в создание постоянной адреса, но значение объекта должно не могут быть доступны с помощью этих операторов.
следовательно, вы можете безопасно инициализировать его с постоянным выражением в этой форме, потому что такой составной литерал действительно обозначает lvalue объекта, который имеет статическую продолжительность хранения:
#include <stdio.h>
struct Test
{
int a;
};
static struct Test *tt = &((struct Test) {1}); /* 2 */
int main(void)
{
printf("%d\n", tt->a);
return 0;
}
Как отмечено, он отлично компилируется с флагами -std=c99 -pedantic-errors
как на gcc
5.2.0, так и на clang
3.6.
Обратите внимание, что в отличие от С++ в C квалификатор const
не влияет на константные выражения.
Ответ 4
ISO C99 делает поддержку сложных литералов (в соответствии с этим). Однако в настоящее время только расширение GNU обеспечивает инициализацию объектов с статической продолжительностью хранения по составным литералам, но только для C90 и С++.
Составной литерал выглядит как литье, содержащее инициализатор. Его значение является объектом типа, указанного в листинге, содержащего элементы, указанные в инициализаторе; это значение lvalue. В качестве расширения GCC поддерживает сложные литералы в режиме C90 и на С++, хотя семантика несколько отличается в С++.
Обычно указанный тип является структурой. Предположим, что struct foo
и структура объявлены как показано:
struct foo {int a; char b[2];} structure;
Вот пример построения struct foo
с составным литералом:
structure = ((struct foo) {x + y, 'a', 0});
Это эквивалентно написанию следующего:
{
struct foo temp = {x + y, 'a', 0};
structure = temp;
}
Расширение GCC:
В качестве расширения GNU GCC позволяет инициализировать объекты со статической продолжительностью хранения сложными литералами (что невозможно в ISO C99, поскольку инициализатор не является константой). Он обрабатывается так, как будто объект инициализируется только при включенном списке скобок, если типы составного литерала и объекта совпадают. Список инициализаторов составного литерала должен быть постоянным. Если инициализированный объект имеет тип массива неизвестного размера, размер определяется размером составного литерала.
static struct foo x = (struct foo) {1, 'a', 'b'};
static int y[] = (int []) {1, 2, 3};
static int z[] = (int [3]) {1};
Примечание:
Теги компилятора на вашем посту включают только GCC; однако вы делаете сравнения с C99 (и несколькими версиями GCC). Важно отметить, что GCC быстрее добавляет расширенные возможности своим компиляторам, чем более крупные стандартные группы C. Это иногда приводит к ошибочному поведению и несоответствиям между версиями. Также важно отметить, что расширения для известного и популярного компилятора, но которые не соответствуют принятому стандарту C, приводят к потенциально не переносимому коду. Всегда стоит подумать о целевых клиентах, решив использовать расширение, которое еще не было принято более крупными рабочими группами C/организациями по стандартизации. (См. ISO (Wikipedia) и ANSI (Wikipedia). )
Существует несколько примеров, когда более мелкие более гибкие рабочие группы или комитеты Open Source C ответили на пользовательскую базу, выразив заинтересованность, добавив расширения. Например, расширение диапазона ящиков переключателя.
Ответ 5
Указание стандарта C11
, глава §6.5.2.5, Компонентные литералы, параграф 3 (основное внимание)
Постфиксное выражение, состоящее из имени типа в скобках, сопровождаемое списком инициализаций, заключенным в скобки, является составным литералом. Он предоставляет неназванный объект, значение которого задается в списке инициализаторов.
Итак, составной литерал является протектором как неназванный объект, который не считается постоянной времени компиляции.
Так же, как вы не можете использовать другую переменную для инициализации статической переменной, начиная с C99, вы не можете использовать этот составной литерал для инициализации статической переменной.