Путаница по поводу инициализации массива в C - PullRequest
0 голосов
/ 13 сентября 2018

Если в языке 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] выводится 10?Это неопределенное поведение?

Примечание: Этот вопрос был задан в интервью.

Ответы [ 7 ]

0 голосов
/ 19 сентября 2018

Назначение a[2]= 1 - это выражение со значением 1, и вы, по сути, написали int a[5]= { 1 }; (с побочным эффектом, что a[2] также назначено 1).

0 голосов
/ 19 ноября 2018

Я полагаю, что 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.

0 голосов
/ 16 сентября 2018

Я думаю, что стандарт C11 покрывает это поведение и говорит, что результат не указан , и я не думаю, что C18 внес какие-либо соответствующие изменения в этой области.

Стандартный языкне легко разобрать.Соответствующий раздел стандарта: §6.7.9 Инициализация .Синтаксис задокументирован как:

initializer:
assignment-expression
{ initializer-list }
{ initializer-list , }
initializer-list:
designationopt initializer
initializer-list , designationopt initializer
designation:
designator-list =
designator-list:
designator
designator-list designator
designator:
[ constant-expression ]
. identifier

Обратите внимание, что одним из терминов является выражение-присваивание и, поскольку a[2] = 1, несомненно, является выражением присваивания, оно разрешено внутри инициализаторов для массивов с нестатической продолжительностью:

§4 Все выражения в инициализаторе для объекта, который имеет статические илипродолжительность хранения потока должна быть константными выражениями или строковыми литералами.

Один из ключевых параграфов:

§19 Инициализация должна происходить в порядке списка инициализаторов, каждый инициализатор предоставляетсядля конкретногоподобъект переопределяет любой ранее перечисленный инициализатор для того же подобъекта; 151) все подобъекты, которые не инициализированы явно, должны быть неявно инициализированы так же, как объекты со статической продолжительностью хранения.

151) Любой инициализатор подобъекта, который переопределен и поэтому не используется для инициализации подобъекта, может вообще не оцениваться.

И еще один ключевой абзац:

§23. Оценки выражений списка инициализации неопределенно упорядочены по отношению друг к другу, и, следовательно, порядок возникновения побочных эффектов не определен. 152)

152) В частности, порядок оценки не должен совпадать с порядком инициализации подобъекта.

Я вполне уверен, что параграф §23 указывает, что запись в вопросе:

int a[5] = { a[2] = 1 };

приводит к неопределенному поведению.Присвоение a[2] является побочным эффектом, и порядок вычисления выражений последовательно определяется по отношению друг к другу.Следовательно, я не думаю, что есть способ обратиться к стандарту и утверждать, что определенный компилятор обрабатывает это правильно или неправильно.

0 голосов
/ 13 сентября 2018

Я пытаюсь дать короткий и простой ответ на головоломку: int a[5] = { a[2] = 1 };

  1. Первый a[2] = 1 установлен. Это означает, что массив говорит: 0 0 1 0 0
  2. Но вот, учитывая, что вы сделали это в скобках { }, которые используются для инициализации массива по порядку, он принимает первое значение (равное 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 шагов:

0 0 0 1 0 0
1 0 0 1 0 0
1 0 0 1 2 0
1 2 0 1 2 0
1 2 0 1 2 3
1 2 3 1 2 3
0 голосов
/ 13 сентября 2018

Мое понимание a[2]=1 возвращает значение 1 , поэтому код становится

int a[5]={a[2]=1} --> int a[5]={1}

int a[5]={1} назначить значение для a [0] = 1

Следовательно, выведите 1 для a [0]

Например

char str[10]={‘H’,‘a’,‘i’};


char str[0] = ‘H’;
char str[1] = ‘a’;
char str[2] = ‘i;
0 голосов
/ 13 сентября 2018

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 должна быть локальной переменной.

    6.7.8 Инициализация

    Все выражения в инициализаторе для объекта со статической продолжительностью хранения должны быть константными выражениями или строковыми литералами.
  • a находится в области действия в своей собственной инициализации.

    6.2.1 Области применения идентификаторов

    Теги структуры, объединения и перечисления имеют область действия, которая начинается сразу после появления тега в спецификаторе типа, который объявляет тег.Каждая константа перечисления имеет область действия, которая начинается сразу после появления ее определяющего перечислителя в списке перечислителей. Любой другой идентификатор имеет область видимости, которая начинается сразу после завершения его объявления.
  • a жив в своей собственной инициализации.

    6.2.4 Длительность хранения объектов

    Объект, идентификатор которого объявлен без привязки и без спецификатора класса хранения static имеет длительность автоматического хранения .

  • Для такихобъект, у которого нет типа массива переменной длины, его время жизни простирается от входа в блок, с которым он связан, до тех пор, пока выполнение этого блока не закончится .(Ввод закрытого блока или вызов функции приостанавливает, но не прекращает выполнение текущего блока.) Если блок вводится рекурсивно, каждый раз создается новый экземпляр объекта.Начальное значение объекта не определено.Если для объекта указана инициализация, она выполняется каждый раз, когда достигается объявление при выполнении блока;в противном случае значение становится неопределенным при каждом достижении декларации.

  • После a[2]=1.

    существует точка последовательности.

    6.8 Заявления и блоки

    A полное выражение - это выражение, которое не является частью другого выражения или объявления.Каждое из следующего является полным выражением: инициализатор ;выражение в выражении выражения;управляющее выражение оператора выбора (if или switch);управляющее выражение оператора while или do;каждое из (необязательных) выражений оператора for;(необязательное) выражение в операторе return. Конец полного выражения является точкой последовательности.
  • Инициализация выполняется в порядке списка инициализаторов.

    6.7.8 Инициализация

    Каждый заключенный в скобки список инициализаторов имеет связанный текущий объект .При отсутствии обозначений подобъекты текущего объекта инициализируются в порядке, соответствующемтип текущего объекта: элементы массива в порядке возрастания индекса, элементы структуры в порядке объявления и первый именованный член объединения. [...]
  • Инициализация должна происходить в порядке списка инициализаторов, каждый инициализатор предоставляется для конкретный подобъект переопределяет любой ранее указанный инициализатор для того же подобъекта; все подобъекты, которые не инициализированы явно, должны быть инициализированы неявно так же, как объекты со статической продолжительностью хранения.
  • Однако выражения инициализатора не обязательно оцениваются по порядку.

    6.7.8 Инициализация

    1. Порядок появления побочных эффектов в выражениях списка инициализации не определено.

Тем не менее, некоторые вопросы остаются без ответа:

  • Точки последовательности вообще актуальны? Основное правило:

    6,5 выражений

    1. Между предыдущей и следующей точкой последовательности объект должен иметь свое сохраненное значение изменяется не более одного раза путем вычисления выражения . Кроме того, предыдущее значение должен быть прочитан только для определения значения, которое будет сохранено.

    a[2] = 1 является выражением, но инициализация не является.

    Это слегка противоречит Приложению J:

    J.2 Неопределенное поведение

    • Между двумя точками последовательности объект изменяется более одного раза или изменяется а предыдущее значение считывается иначе, чем для определения значения, которое будет сохранено (6.5).

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

  • Как инициализируются подобъекты по отношению к выражениям инициализатора? Все ли инициализаторы оцениваются первыми (в некотором порядке), затем подобъекты инициализируются с результатами (в порядке списка инициализаторов)? Или их можно чередовать?


Я думаю int a[5] = { a[2] = 1 } выполняется следующим образом:

  1. Память для a выделяется при вводе содержащего ее блока. На данный момент содержимое не определено.
  2. Выполняется (только) инициализатор (a[2] = 1), за которым следует точка последовательности. Это сохраняет 1 в a[2] и возвращает 1.
  3. То, что 1 используется для инициализации a[0] (первый инициализатор инициализирует первый подобъект).

Но здесь все становится нечетким, потому что остальные элементы (a[1], a[2], a[3], a[4]) должны быть инициализированы в 0, но неясно, когда: это случалось раньше a[2] = 1 оценивается? Если это так, a[2] = 1 будет "выигрывать" и перезаписывать a[2], но будет ли это присваивание иметь неопределенное поведение, потому что нет никакой последовательности между нулевой инициализацией и выражением присваивания? Точки последовательности даже актуальны (см. Выше)? Или инициализация нуля происходит после оценки всех инициализаторов? Если это так, a[2] должен в конечном итоге стать 0.

Поскольку стандарт С не дает четкого определения того, что здесь происходит, я считаю, что поведение не определено (из-за упущения).

0 голосов
/ 13 сентября 2018

Я не понимаю, почему a[0] печатает 1 вместо 0?

Предположительно a[2]=1 сначала инициализирует a[2], а результатвыражение используется для инициализации a[0].

Начиная с N2176 (черновик C17):

6.7.9 Инициализация

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

Поэтому может показаться, что вывод 1 0 0 0 0 также был бы возможен.

Вывод: не пишите инициализаторы, которые изменяют инициализированную переменную на лету.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...