Определенное поведение для выражений - PullRequest
11 голосов
/ 29 января 2012

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

Ответы [ 2 ]

15 голосов
/ 30 января 2012

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

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

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

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

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

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

Но это отражает то, что если подвыражение B зависит от результата подвыражения 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 имеет новую модель для такого рода оценки выражений - или, скорее, он выражает одну и ту же модель в разных словах. Вместо «точек последовательности» речь идет о последовательных побочных эффектах до или после друг друга или о непоследовательности относительно друг друга. Это ясно дает понять, что если подвыражение B зависит от результата подвыражения A, то A должно быть оценено , прежде чем B может быть оценено.

В набросок N1570 , раздел 6.5 гласит:

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

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

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

2 голосов
/ 30 января 2012

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

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

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

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

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

...