На языке C, если инициализировать массив следующим образом:
int a[5] = {1,2};
тогда все элементы массива, которые не инициализированы явно, будут неявно инициализированы нулями.
Но, если я инициализирую массив следующим образом:
int a[5]={a[2]=1};
printf("%d %d %d %d %d\n", a[0], a[1],a[2], a[3], a[4]);
вывод:
1 0 1 0 0
Я не понимаю, почему a[0]
печать 1
вместо 0
? Это неопределенное поведение?
Примечание: этот вопрос был задан в интервью.
a[2]=1
оценивается как1
.a[2] = 1
равно1
, но я не уверен, разрешено ли вам принимать результат назначенного выражения инициализатора в качестве значения первого элемента. Тот факт, что вы добавили тег «адвокат», означает, что я думаю, что нам нужен ответ со ссылкой на стандарт.Ответы:
TL; DR: Я не думаю, что поведение
int a[5]={a[2]=1};
хорошо определено, по крайней мере, в C99.Забавно то, что единственный бит, который имеет смысл для меня, - это та часть, о которой вы спрашиваете:
a[0]
установлено значение,1
потому что оператор присваивания возвращает значение, которое было присвоено. Все остальное непонятно.Если код был
int a[5] = { [2] = 1 }
, все уже были бы легко: Это назначенный инициализатором установкиa[2]
для1
и всего остального0
. Но с{ a[2] = 1 }
у нас есть неназначенный инициализатор, содержащий выражение присваивания, и мы падаем в кроличью нору.Вот что я нашел на данный момент:
a
должна быть локальной переменной.a[2] = 1
не является постоянным выражением, поэтомуa
должно иметь автоматическое сохранение.a
входит в область его собственной инициализации.Декларатор есть
a[5]
, поэтому переменные находятся в области видимости при их собственной инициализации.a
жив при собственной инициализации.После идет точка последовательности
a[2]=1
.Следует отметить , что , например , в
int foo[] = { 1, 2, 3 }
в{ 1, 2, 3 }
части является скобка огороженный список инициализаторов, каждый из которых имеет точку последовательности после него.Инициализация выполняется в порядке списка инициализаторов.
Однако выражения инициализатора не обязательно вычисляются по порядку.
Однако некоторые вопросы остаются без ответа:
Имеются ли вообще точки последовательности? Основное правило:
a[2] = 1
это выражение, а инициализация - нет.Этому немного противоречит Приложение J:
В приложении J говорится, что учитываются любые модификации, а не только модификации выражениями. Но учитывая, что приложения не являются нормативными, мы, вероятно, можем игнорировать это.
Каким образом инициализации подобъекта упорядочиваются относительно выражений инициализатора? Все ли инициализаторы сначала оцениваются (в некотором порядке), затем подобъекты инициализируются результатами (в порядке списка инициализаторов)? Или их можно чередовать?
Думаю
int a[5] = { a[2] = 1 }
выполняется следующим образом:a
выделяется при вводе содержащего его блока. На данный момент содержание не определено.a[2] = 1
), за которым следует точка последовательности. Это сохраняет1
вa[2]
и возвращается1
.1
используется для инициализацииa[0]
(первый инициализатор инициализирует первый подобъект).Но здесь вещи становятся нечеткими , так как остальные элементы (
a[1]
,a[2]
,a[3]
,a[4]
) должны быть инициализированы0
, но не ясно , когда: Имеют ли это произойдет доa[2] = 1
оцениваемого? Если да, тоa[2] = 1
будет ли "победить" и перезаписатьa[2]
, но будет ли это присвоение иметь неопределенное поведение, потому что между нулевой инициализацией и выражением присваивания нет точки последовательности? Имеют ли значение точки последовательности (см. Выше)? Или нулевая инициализация происходит после оценки всех инициализаторов? Если так,a[2]
должно быть0
.Поскольку стандарт C четко не определяет, что здесь происходит, я считаю, что поведение не определено (по пропуску).
источник
a[0]
подобъект до оценки его инициализатора, а оценка любого инициализатора включает точку последовательности (потому что это «полное выражение»). Поэтому я считаю, что изменение подобъекта, который мы инициализируем, - это честная игра.Предположительно сначала
a[2]=1
инициализируетсяa[2]
, а результат выражения используется для инициализацииa[0]
.Из N2176 (проект C17):
Казалось бы, выход
1 0 0 0 0
тоже был возможен.Вывод: не пишите инициализаторы, которые изменяют инициализированную переменную на лету.
источник
{...}
выражение , которое инициализируетa[2]
к0
иa[2]=1
суб-выражение , которое инициализируетa[2]
к1
.{...}
- это список инициализаторов в фигурных скобках. Это не выражение.Я думаю, что стандарт C11 охватывает это поведение и говорит, что результат не указан , и я не думаю, что C18 внес какие-либо существенные изменения в этой области.
Стандартный язык разобрать непросто. Соответствующий раздел стандарта - это §6.7.9 Инициализация . Синтаксис задокументирован как:
Обратите внимание, что одним из терминов является выражение-присваивание , и поскольку
a[2] = 1
это, несомненно, выражение присваивания, оно разрешено внутри инициализаторов для массивов с нестатической продолжительностью:Один из ключевых абзацев:
И еще один ключевой абзац:
Я почти уверен, что параграф §23 указывает на то, что обозначения в вопросе:
приводит к неопределенному поведению. Присваивание
a[2]
является побочным эффектом, и порядок оценки выражений неопределенно упорядочен по отношению друг к другу. Следовательно, я не думаю, что есть способ апеллировать к стандарту и утверждать, что конкретный компилятор обрабатывает это правильно или неправильно.источник
Мое понимание
a[2]=1
возвращает значение 1, поэтому код становитсяint a[5]={1}
присвоить значение для [0] = 1Следовательно, он выводит 1 для [0]
Например
источник
Я стараюсь дать короткий и простой ответ на загадку:
int a[5] = { a[2] = 1 };
a[2] = 1
установлен. Это означает, что в массиве написано:0 0 1 0 0
{ }
скобках, которые используются для инициализации массива по порядку, он берет первое значение (которое есть1
) и устанавливает его вa[0]
. Это какint a[5] = { a[2] };
бы остаться там, где мы уже попалиa[2] = 1
. Результирующий массив теперь:1 0 1 0 0
Другой пример:
int a[6] = { a[3] = 1, a[4] = 2, a[5] = 3 };
- Несмотря на то, что порядок несколько произвольный, предполагая, что он идет слева направо, он будет состоять из следующих 6 шагов:источник
A = B = C = 5
не является объявлением (или инициализацией). Это нормальное выражение, которое разбирается как,A = (B = (C = 5))
потому что=
оператор правильно ассоциативен. На самом деле это не помогает объяснить, как работает инициализация. Фактически массив начинает существовать, когда вводится блок, в котором он определен, что может быть задолго до выполнения фактического определения.a[2] = 1
выражения инициализатора? Наблюдаемый результат такой же, как если бы он был, но стандарт, кажется, не указывает, что это должно быть так. Это центр спора, и этот ответ полностью игнорирует его.Присваивание
a[2]= 1
- это выражение, имеющее значение1
, и вы, по сути, написалиint a[5]= { 1 };
(сa[2]
присваиваемым побочным эффектом1
).источник
Я полагаю, что
int a[5]={ a[2]=1 };
это хороший пример для программиста, стреляющего себе в ногу.У меня может возникнуть соблазн подумать, что вы имели в виду
int a[5]={ [2]=1 };
что было бы назначенным C99 элементом установки инициализатора 2 равным 1, а остальным - нулем.В том редком случае, который вы действительно имели в виду
int a[5]={ 1 }; a[2]=1;
, это было бы забавным способом написать это. Во всяком случае, это то, к чему сводится ваш код, хотя некоторые здесь указали, что он не очень четко определен, когдаa[2]
фактически выполняется запись . Проблема здесь в том, чтоa[2]=1
это не назначенный инициализатор, а простое присваивание, которое само имеет значение 1.источник