Использование ранее установленных полей при инициализации структуры составными литералами

Каково ожидаемое поведение ссылки на ранее установленные поля при объявлении начальных значений структуры с использованием составного литерала: В частности, можно ли: struct foo v = { .v1 = ..., .v2 = .v1+1 };

Более полный пример:

struct s {
    int i1 ;
    int i2 ;
} ;

void func(void)
{
     // Case A:
     struct s v1 = { 3 } ;
     v1 = (struct s) ( .i1 = 4, .i2 = v1.i1+10 }
     // always v1.i2=13, NOT 14

     // Case B:
     // Is this legal ?
     struct s v2 = ( .i1 = 1, .i2 = v2.i1+10 }
     // Under GCC, v2.i2 = 11

}

В случае A - стандарт ясно, что создается «временный» составной литерал на основе значения v1 перед присвоением (v1.i1=3), в результате чего v2.i2 = 13. Объяснение состоит в том, что это в основном:

     struct s v1 = { 3 } ;
     struct s temp = ( .i1 = 4, .i2 = v1.i1+10 }
     v1 = temp ;

Для случая B, по крайней мере, с GCC, выглядит так, как будто можно ссылаться на ранее установленные значения в том же операторе SAME - в этом случае i2 установлено в 11, подразумевая, что это НЕ было реализовано с использованием временной переменной, которая имела бы результаты в v2. я2 = 10;

     // NOT the same as struct v1 = { ... };
     struct s temp = ( .i1 = 4, .i2 = v1.i1+10 }
     struct s v1 = temp ;

Мой вопрос: что говорит стандарт C99/C23 о втором случае? это обязательное поведение, поведение, специфичное для реализации, или неопределенное поведение, которое компиляторы должны помечать как предупреждение/ошибку?

Я попробовал описанное выше с GCC9 (-Wall, -Wextra), а также с Coverity - никаких проблем не возникло.

Если я правильно прочитал эту ссылку на инициализацию структуры, случай B действителен. Переменная существует, когда происходит инициализация (именно поэтому, например, int a = a; будет строиться), а инициализация членов структуры происходит в том порядке, в котором вы их записываете. Поэтому, когда вы инициализируете i2, v2.i1 уже будет инициализирован.

Some programmer dude 14.07.2024 09:56

Случай A сложен, поскольку {3} является инициализацией v1.i1, а v1.i2 инициализируется 0 по умолчанию. Теперь v1 = (struct s) { .i1 = 4, .i2 = v1.i1+10 } — это задание (обратите внимание на фиксированный {). На этом этапе v1.i1 == 3 из инициализации, поскольку нет идентификатора v1, связанного с составным литералом. Таким образом, результат будет v1.v2 == 13, а не 14.

David C. Rankin 14.07.2024 10:56

О, добавьте -pedantic -Werror в строку компиляции, и появится ваш -Wmissing-field-initializers (жалоба на оригинал {3}).

David C. Rankin 14.07.2024 11:01
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
3
61
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Стандарт C ничего не говорит о том, какие элементы или члены агрегатов инициализируются до оценки каких-либо инициализаторов.

Стандарт содержит формулировку о порядке инициализации элементов или членов (в проекте C 2023 N3096 ​​6.7.10). Однако в лучшем случае этот порядок сообщает нам только порядок, в котором значения хранятся в элементах или членах. Здесь ничего не говорится о том, когда эти значения были подготовлены. 6.7.10 24 говорит:

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

Эта формулировка впервые появилась в C 2011 6.7.9 23 (определено из проекта N1570). (Тот факт, что он отсутствовал до этого, не означает, что существовало секвенирование; это означает, что стандарт C ничего не говорил об этом и, следовательно, ничего не утверждал о секвенировании.) Эта формулировка подтверждает, что выражения не обязательно вычисляются в том же порядке, что и элементы или члены инициализируются, поскольку это наложит на них порядок.

Таким образом, в struct s v2 = ( .i1 = 1, .i2 = v2.i1+10 } компилятор может сгенерировать программу, которая работает в следующем порядке:

  1. Оцените 1.
  2. Оцените v2.i1+10.
  3. Инициализируйте .i1 для результата шага 1.
  4. Инициализируйте .i2 для результата шага 2.

В этом порядке v2.i1 не инициализируется во время выполнения шага 2.

Другие допустимые порядки включают 2 1 3 4 и 1 3 2 4, а также переупорядочение, разделяющее v2.i1+10 на оценки v2.i1, 10 и сложение.

Основная цель определения порядка инициализации состоит в том, чтобы определить, какие элементы или члены инициализируются, когда обозначения отсутствуют. То есть при разборе кода компилятор принимает инициализаторы, соответствующие элементам или членам в том порядке, в котором они появляются в агрегате. Вторичная цель фигурирует в проекте C 2020 N3096 ​​6.7.10 20:

Инициализация должна происходить в порядке списка инициализаторов, каждый инициализатор, предоставленный для конкретного подобъекта, переопределяет любой ранее указанный инициализатор для того же подобъекта;…

Это говорит нам, какой инициализатор используется, если элемент или член появляется при инициализации несколько раз, но опять же ничего не говорится о порядке, в котором оцениваются инициализаторы.

Дополнительное обсуждение

Я подозреваю, что обсуждение «порядка» в C 2023 6.7.10 было мотивировано исключительно для того, чтобы указать интерпретацию того, какие инициализаторы применяются к каким элементам или членам, и не предназначалось для того, чтобы оказать какое-либо влияние на состояние программы в работающей программе. Единственный способ, которым я вижу, что это может повлиять на наблюдаемое поведение программы (кроме попыток ссылаться на ранее инициализированные элементы или члены), - это наличие инициализируемых изменчивых объектов. Это вызовет некоторые проблемы. Является ли инициализация доступом к объекту с целью volatile? Если элемент или член появляется дважды при инициализации, означает ли тот факт, что второе появление «переопределяет» первое, первое подавляется и никогда не появляется, или что оно появляется, но позже перезаписывается?

Другие вопросы по теме

JavaFX не инициализирует представления
Есть ли выигрыш в производительности от использования алгоритмов c++ std(::ranges)::uninitialized_... и стоит ли не использовать constexpr?
Приводит ли наличие вектора, содержащего структуры с неинициализированными членами, к неопределенному поведению?
Попытка инициализировать объекты класса внутри структуры приводит к ошибкам сегментации
Существует ли альтернативный синтаксис для инициализации константного указателя на константные данные?
Почему Swift разрешает использование переменных перед объявлением в глобальной области, но не внутри функций?
Не удалось вычислить аргумент шаблона класса при использовании назначенных инициализаторов с инициализацией списка
Как инициализировать инициализатор_список?
Инициализация статических конечных полей в системном классе Java в JDK 14 и последующих версиях
Можете ли вы объявить указатель C с собственным адресом?