Приводит ли a[a[0]] = 1 к неопределенному поведению?


этот код C99 производит неопределенное поведение?

#include <stdio.h>

int main() {
  int a[3] = {0, 0, 0};
  a[a[0]] = 1;
  printf("a[0] = %dn", a[0]);
  return 0;
}

в заявлении a[a[0]] = 1;,a[0] читается и изменяется.

Я посмотрел проект N1124 ISO / IEC 9899. Он говорит (в 6.5 выражения):

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

он не упоминает чтение объекта, чтобы определить сам объект, который будет изменен. Таким образом, это утверждение может привести к неопределенному поведению.

однако, я чувствую это странно. Действительно ли это приводит к неопределенному поведению?

(Я также хочу знать об этой проблеме в других версиях ISO C.)

5 54

5 ответов:

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

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

он пытается сказать следующее: если чтение старого значения гарантированно произойдет раньше, чем запись нового значения, то это нормально. В противном случае это UB. И, конечно же, это требование, чтобы новое значение было вычислено раньше так написано.

(конечно, описание, которое я только что написал, будет найдено некоторыми более расплывчатым, чем стандартный текст!)

x = x + 5 правильно, потому что это не возможно выработать x + 5 без знания x. Однако a[i] = i++ - это неправильно, потому что читаем о i С левой стороны не требуется для того, чтобы выработать новые ценности в магазине в i. (Эти два чтения несколько отдельно.)

теперь вернемся к вашему коду. Я думаю, что это хорошо определенное поведение, потому что чтение a[0] для того, чтобы определить индекс массива гарантированно произойдет до записи.

мы не можем писать, пока не определимся, куда писать. И мы не знаем, куда писать, пока не прочитаем a[0]. Следовательно, читать должно прийти прежде, чем писать, так нет УБ.

кто-то прокомментировал точки последовательности. В C99 нет точка последовательности в этом выражении, поэтому точки последовательности не входят в это обсуждение.

этот код C99 производит неопределенное поведение?

нет. Он не будет производить неопределенное поведение. a[0] изменяется только один раз между двумя точки последовательности (первая точка последовательности находится в конце инициализатора int a[3] = {0, 0, 0}; и второй-после полного выражения a[a[0]] = 1).

он не упоминает чтение объекта, чтобы определить сам объект, который будет изменен. Таким образом, это утверждение может привести к неопределенному поведение.

объект может быть прочитан несколько раз, чтобы изменить себя и свое совершенно определенное поведение. Посмотрите на этот пример

int x = 10;
x = x*x + 2*x + x%5;   

второе утверждение цитаты гласит:

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

все x в приведенном выше выражении читается для определения значения объекта x сам по себе.


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

i = i++;

поставляется под UB (две модификации между предыдущими и следующими точками последовательности).

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

a[i++] = i;
j = (i = 2) + i;  

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


в стандарте C11 это было изменено на

6.5 выражения:

если побочный эффект на скалярный объект-это неупорядоченное относительно другой побочный эффект в тот же скалярный объект или значение, вычисление, используя значение же скалярный объект, поведение неопределено. [...]

в выражение a[a[0]] = 1, есть только один побочный эффект a[0] и вычисление значения Индекса a[0] секвенируется перед вычислением значения a[a[0]].

C99 представляет собой перечисление всех точек последовательности в приложении C. В конце

a[a[0]] = 1;

потому что это полный оператор выражения, но внутри нет точек последовательности. Хотя логика подсказывает, что подвыражение a[0] должен быть вычислен первым, и результат, используемый для определения, какому элементу массива присваивается значение, правила последовательности не обеспечивают его. Когда начальное значение a[0] и 0,a[0] Как читать и написано между двумя точками последовательности, и чтение не С целью определения того, какое значение писать. Согласно C99 6.5 / 2, поведение оценки выражения поэтому не определено, но на практике я не думаю, что вам нужно беспокоиться об этом.

C11 лучше в этом отношении. В пункте 1 раздела 6.5 говорится

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

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

C11 тем не менее проходит для нас на этом, так как спецификации для операторов присваивания обеспечивают необходимую последовательность (C11 6.5.16(3)):

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

(напротив, C99 просто говорит, что обновление сохраненного значения левого операнда происходит между предыдущей и следующей точками последовательности.) С разделами 6.5 и 6.5.16 вместе, тогда C11 дает четко определенную последовательность: внутренний [] оценивается перед внешним [], который вычисляется до обновления сохраненного значения. Это удовлетворяет версии C11 6.5(2), поэтому в C11 определяется поведение вычисления выражения.

значение хорошо определено, если a[0] содержит значение, которое не является допустимым индексом массива (т. е. в коде это не отрицательный и не превышает 3). Вы можете изменить код на более читаемый и эквивалентный

 index = a[0];
 a[index] = 1;    /* still UB if index < 0 || index >= 3 */

в выражении a[a[0]] = 1 надо оценивать a[0] первый. Если a[0] бывает ноль, то a[0] будет изменен. Но нет никакого способа для компилятора (за исключением несоблюдения стандарта) изменить порядок оценки и модификации a[0] перед попыткой прочитать его значение.

побочный эффект включает в себя модификацию объекта1.

стандарт C говорит, что поведение не определено, если побочный эффект на объект не связан с побочным эффектом на тот же объект или вычислением значения с использованием значения того же объекта2.

объект a[0] в этом выражении модифицируется (побочный эффект) и его значение (вычисление значения) используется для определения индекса. Казалось бы, это выражение дает неопределенный поведение:

a[a[0]] = 1

однако текст в операторах присваивания в стандарте объясняет, что вычисление значений как левого, так и правого операндов оператора =, секвенируется перед изменением левого операнда3.

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


1 (Цитата из ISO / IEC 9899: 201x 5.1.2.3 исполнение программы 2):
Доступ к изменчивому объекту, изменение объекта, изменение файла или вызов функции что делает любая из этих операций все побочные эффекты, которые являются изменениями в состоянии среда выполнения.

2 (Цитируется по ISO / IEC 9899: 201x 6.5 выражения 2):
Если побочный эффект на скалярный объект не имеет последовательности относительно любого другого побочного эффекта на один и тот же объект скалярного или вычисление значения с использованием значения одного и того же скаляра объект, поведение не определено.

3 (Цитируется по ISO / IEC 9899: 201x 6.5.16 операторы присваивания 3):
Побочным эффектом обновления сохраненного значения левого операнда является секвенируется после вычисления значений левого и правого операндов. Оценки деятельности операнды не имеют последовательности.