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

Определенное поведение для выражений

Стандарт C99 говорит в $6.5.2.

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

(выделение мной)

Далее следует отметить, что следующий пример действителен (что кажется очевидным вначале)

a[i] = i;

Пока он явно не указывает, что a и i.

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

int i = 0, *a = &i;
a[i] = i;

Это не изменит значение i, но получите доступ к значению i, чтобы определить адрес, куда поместить значение. Или не имеет значения, что мы присваиваем значение i, которое уже хранится в i? Просветите свет.


Бонусный вопрос; Что насчет a[i]++ или a[i] = 1?

4b9b3361

Ответ 1

Первое предложение:

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

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

Следующее предложение:

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

кажется неинтуитивным сначала (и вторым) взглядом; почему цель, для которой считывается значение, влияет на то, было ли выражение определено поведение?

Но то, что он отражает, состоит в том, что если подвыражение В зависит от результата подвыражения A, тогда A необходимо оценить до того, как B можно оценить. В стандартах C90 и C99 это не указывается явно.

Более явное нарушение этого предложения, приведенное в примере в сноске, следующее:

a[i++] = i; /* undefined behavior */

Предполагая, что a является объявленным объектом массива, а i является объявленным целым объектом (без указателя или макроопределения), ни один объект не изменяется более одного раза, поэтому он не нарушает первое предложение. Но оценка i++ на LHS определяет, какой объект должен быть изменен, а оценка i в RHS определяет значение, которое будет храниться в этом объекте, - и относительный порядок операции чтения на RHS и операция записи на LHS не определена. Опять же, язык мог потребовать, чтобы подвыражения были оценены в некотором неуказанном порядке, но вместо этого он оставил все поведение undefined, чтобы обеспечить более агрессивную оптимизацию.

В вашем примере:

int i = 0, *a = &i;
a[i] = i; /* undefined behavior (I think) */

предыдущее значение i считывается как для определения сохраняемого значения, так и для определения того, какой объект он будет хранить. Поскольку a[i] относится к i (но только потому, что i==0), изменение значения i изменило бы объект, к которому относится lvalue a[i]. В этом случае значение, хранящееся в i, совпадает с значением, которое уже было там записано (0), но стандарт не делает исключение для магазинов, которые хранят одно и то же значение. Я считаю, что поведение undefined. (Конечно, пример в стандарте не был предназначен для покрытия этого случая, он подразумевает, что a является объявленным объектом массива, не связанным с i.)

Как для примера, разрешенного стандартом:

int a[10], i = 0; /* implicit, not stated in standard */
a[i] = i;

можно было бы интерпретировать стандарт, чтобы сказать, что он undefined. Но я думаю, что второе предложение, относящееся к "предыдущему значению", относится только к значению объекта, который был изменен выражением. i никогда не изменяется выражением, поэтому конфликта нет. Значение i используется как для определения объекта, подлежащего изменению назначением, так и для значения, которое должно быть там сохранено, но это нормально, так как значение i никогда не изменяется. Значение i не является "предшествующим значением", это просто значение.

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

В проект N1570, в разделе 6.5 говорится:

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

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

3 Группировка операторов и операндов обозначается синтаксисом.     За исключением случаев, указанных ниже, побочные эффекты и вычисления значений     подвыражений не имеет значения.

Ответ 2

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

Сохраняет ли значение 0 в объект, который уже содержит значение 0 count как "изменение сохраненного значения"? По простому английскому определению "изменить" я должен был бы сказать нет; оставляя что-то неизменным, является противоположностью его модификации.

Однако ясно, что это будет поведение undefined:

int i = 0, *a = &i;
a[i] = 1;

Здесь нет никаких сомнений в том, что сохраненное значение считывается с целью, отличной от определения сохраняемого значения (значение, которое необходимо сохранить, является константой) и что значение i изменяется.