Подтвердить что ты не робот

Выполняет ли [a [0]] = 1 поведение undefined?

Создает ли этот код C99 поведение undefined?

#include <stdio.h>

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

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

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


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

Мы не можем писать, пока не определим, где писать. И мы не знаем, где писать, пока не прочитаем a[0]. Поэтому чтение должно появиться перед записью, поэтому нет UB.

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

Ответ 2

Создает ли этот код C99 поведение undefined?

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

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

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

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 Выражения:

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

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

Ответ 3

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

a[a[0]] = 1;

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

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

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

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

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

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

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

Ответ 4

Значение корректно определено, если 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], прежде чем пытаться прочитать его значение.

Ответ 5

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

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

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

a[a[0]] = 1

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

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


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

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

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