Имеет ли 'a [i] = i;' всегда приводят к хорошо определенному поведению?
Есть несколько интересных вопросов, поднятых здесь относительно поведения undefined в C. Один из них (слегка изменен)
Является ли следующий фрагмент кода результатом undefined?
int i = 0, *a = &i; // Line 1
a[i] = i + 1; // Line 2
Так как нет конкретного ответа на эту часть вопроса, и я заинтересован в том, чтобы знать поведение в С++, я поднимаю его снова здесь.
Правило № 2 из Undefined Поведение и точки последовательности говорит
Кроме того, к предыдущему значению следует обращаться только для определения значения, которое нужно сохранить
Очевидно, что в приведенном выше примере дважды обращается к значению: a[i]
(lhs) и i
(rhs), и только один из них (rhs) определяет значение, которое нужно сохранить.
Ли строка 2 нарушает правило выше и приводит к поведению undefined в С++ 03?
Существует некоторая путаница в отношении того, изменяется ли i
в строке 2?
Да, это изменено!
Ответы
Ответ 1
Это приведет к поведению undefined в С++ 03 и корректному поведению в С++ 11.
С++ 03: undefined Behvaior
Из стандарта С++ 03, раздел 5, пункт 4:
Между предыдущей и следующей точкой последовательности скалярный объект должен иметь значение, которое его значение хранится не более одного раза путем оценки выражения. Кроме того, к предыдущему значению следует обращаться только для определения значения, которое необходимо сохранить.
Обратите внимание на второе предложение: предыдущее значение i
может использоваться только для определения значения, которое необходимо сохранить. Но здесь он также используется для определения индекса массива. Так как это назначение изменит i
, a[0] = i+1
будет корректно определено, а a[i] = i+1
- нет. Обратите внимание, что присваивание не создает точку последовательности: только конец полного выражения (точка с запятой) делает.
С++ 11: Хорошо определенное поведение:
С++ 11 избавился от понятия точек последовательности и вместо этого определил, какие оценки секвенированы до того.
Из стандарта, раздел 1.9, пункт 15:
Вычисления значений операндов оператора секвенируются перед вычислением значения результата оператора. Если побочный эффект скалярного объекта не зависит от другого побочного эффекта на одном и том же скалярном объекте или вычисления значения с использованием значения одного и того же скалярного объекта, поведение undefined.
Оба операнда оператора присваивания секвенированы перед фактическим присваиванием. Таким образом, будут оцениваться как a[i]
, так и i+1
, и только после этого будет изменен i
. Результат хорошо определен.
Ответ 2
int i = 0, *a = &i;
существует точка последовательности между декларациями, поэтому здесь нет UB. Однако обратите внимание, что это плохая идея объявить/определить переменные таким образом. Любой нормальный стандарт кодирования должен сказать вам объявить одну переменную в строке.
a[i] = i;
i
никоим образом не изменяется, поэтому здесь нет UB.
Ответ 3
Разложим ли выражение a[i] = i + 1
?
= -- [] -- a
\ \_ i
\
\_ + -- i
\_ 1
Фактически, a[i]
относится к &i
, однако обратите внимание, что ни a[i]
, ни i+1
не изменяет i
. i
изменяется только тогда, когда выполняется =
(само присваивание).
Так как операнды любой функции нужно оценить до того, как эта функция вступит в силу, это фактически эквивалентно:
void assign(int& address, int value) { address = value; }
assign(a[i], i + 1);
Верно, что =
несколько отличается тем, что он встроен и не приводит к вызову функции, но оценка обоих операндов упорядочена до фактического назначения, поэтому они сначала оцениваются до i
, а a[i]
(который указывает на местоположение i
) присваивается.
Ответ 4
Undefined поведение в этом случае произойдет только в том случае, если вы измените тот же адрес памяти без точки последовательности между изменениями. В частности, спецификация C99, раздел 6.5/2,
Между предыдущей и следующей точкой последовательности объект должен иметь свой запомненное значение, измененное не более одного раза путем оценки выражения. Кроме того, к предыдущему значению следует обращаться только для определения значение, которое необходимо сохранить.
В вашем случае не происходит модификации одного и того же адреса памяти между точками последовательности, поэтому поведение undefined отсутствует.
Ответ 5
Я хотел бы отметить одно: a[i] = i
не всегда ведет к четко определенному поведению. Причина, по которой поведение четко определено в указанном случае, обусловлено начальными значениями i
и a
.
Позвольте мне уточнить:
int i = 1, *a = &i; // Line 1, i initialized to anything other than 0
a[i] = i + 1; // Line 2, all of a sudden we are in buffer over/underflow
Для любого другого начального значения i
мы получаем доступ к другой ячейке памяти из самой i
, которая создает поведение undefined.
Ответ 6
Нет, нет. Первая строка имеет точку последовательности (запятую), поэтому это не поведение undefined:
int i = 0, *a = &i;
Вторая строка совершенно нормальная.
a[i] = i + 1;
Так как i + 1
создает временное значение, i
изменяется только один раз, в присваивании. Однако это будет undefined поведение:
a[i] = i++;