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

Стандарт 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 ?

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

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

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

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

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

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

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

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

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

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

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

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

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

    Что касается примера, который разрешен в стандарте:

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

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

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

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

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

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

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

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

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

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

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

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