Статическая инициализация структуры в 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, вы не можете использовать этот составной литерал для инициализации статической переменной.