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

В чем разница между точкой последовательности и приоритетом оператора?

Рассмотрим пример классической последовательности:

i = i++;

В стандартах на C и С++ указано, что поведение вышеуказанного выражения undefined, поскольку оператор = не связан с точкой последовательности.

Меня смущает то, что ++ имеет более высокий приоритет, чем =, и поэтому приведенное выше выражение на основе приоритета должно сначала оценить i++, а затем выполнить назначение. Таким образом, если мы начинаем с i = 0, мы всегда должны заканчивать с i = 0 (или i = 1, если выражение было i = ++i), а не undefined. Что мне не хватает?

4b9b3361

Ответ 1

Все операторы производят результат. Кроме того, некоторые операторы, такие как оператор присваивания = и составные операторы присваивания (+=, ++, >>= и т.д.), Создают побочные эффекты. Различие между результатами и побочными эффектами лежит в основе этого вопроса.

Приоритет оператора определяет порядок, в котором операторы применяются для получения своих результатов. Например, правила приоритета требуют, чтобы * проходил до +, + до & и т.д.

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

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

Ключевая вещь, которая здесь отсутствует, - это точки последовательности, разделяющие побочные эффекты двух операторов. Это то, что делает поведение undefined.

Ответ 2

Приоритет операторов (и ассоциативность) определяет порядок, в котором выражение анализируется и выполняется. Однако это ничего не говорит о порядке оценки операндов, что является другим термином. Пример:

a() + b() * c()

Приоритет оператора указывает, что результат b() и результат c() должны быть умножены перед добавлением вместе с результатом a().

Однако он ничего не говорит о порядке, в котором эти функции должны выполняться. Этот порядок определяет порядок оценки каждого оператора. Чаще всего порядок оценки неуточнен (неопределенное поведение), что означает, что стандарт позволяет компилятору делать это в любом порядке, который ему нравится. Компилятор не должен документировать этот порядок и не должен вести себя последовательно. Причиной этого является предоставление компиляторам большей свободы в синтаксическом анализе выражения, что означает более быструю компиляцию и, возможно, более быстрый код.

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

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

В приведенном выше примере три операнда не имеют последовательности по отношению друг к другу, что означает, что никакая точка последовательности не определяет порядок оценки.

Поэтому становится проблематичным, когда побочные эффекты вводятся в таких неестественных выражениях. Если мы напишем i++ + i++ * i++, то мы до сих пор не знаем порядка, в котором эти операнды оцениваются, поэтому мы не можем определить, каким будет результат. Это связано с тем, что как +, так и * имеют неуказанный/неточный порядок оценки.

Если бы мы написали i++ || i++ && i++, то поведение было бы корректно определено, поскольку && и || определяет порядок оценки слева направо и есть точка последовательности между оценкой левый и правый операнды. Таким образом, if(i++ || i++ && i++) является совершенно портативным и безопасным (хотя и нечитаемым) кодом.


Что касается выражения i = i++;, проблема здесь в том, что = определяется как (6.5.16):

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

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

И поскольку выражение содержит побочный эффект i++, он вызывает поведение undefined, так как мы не можем знать, сначала ли оценивается операнд i или операнд i++.

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

Ответ 3

Приоритет операторов и порядок оценки - это две разные вещи. Давайте взглянем на них один за другим:

Правило приоритета оператора: В операндах выражений, более жестких к операторам с более высоким приоритетом.

Например

int a = 5;
int b = 10;
int c = 2;
int d;

d = a + b * c;  

В выражении a + b * c приоритет * выше, чем у +, и поэтому b и c будут привязаны к *, и выражение будет проанализировано как a + (b * c).

Порядок оценки:. Он описывает, как операнды будут вычисляться в выражении. В заявлении

 d = a>5 ? a : ++a; 

a гарантированно оценивается перед оценкой ++b или c.
Но для выражения a + (b * c), хотя * имеет более высокий приоритет, чем +, не гарантируется, что a будет оцениваться до или после b или c и даже не b и c, заказанных для их оценки. Даже a, b и c могут оцениваться в любом порядке.

Простым правилом является то, что приоритет оператора не зависит от порядка оценки и наоборот.

В выражении i = i++ более высокий приоритет ++ просто указывает компилятору связать i с оператором ++ и что он. Он ничего не говорит о порядке оценки операндов или о том, какой побочный эффект (один оператор = или один на ++) должен иметь место первым. Компилятор может делать что угодно.

Переименуем i слева от присваивания be il и справа от присваивания (в выражении i++) будет ir, тогда выражение будет выглядеть как

il = ir++     // Note that suffix l and r are used for the sake of clarity.
              // Both il and ir represents the same object.  

Теперь компилятор может свободно оценивать выражение il = ir++ либо как

temp = ir;      // i = 0
ir = ir + 1;    // i = 1   side effect by ++ before assignment
il = temp;      // i = 0   result is 0  

или

temp = ir;      // i = 0
il = temp;      // i = 0   side effect by assignment before ++
ir = ir + 1;    // i = 1   result is 1  

что приводит к двум различным результатам 0 и 1, которые зависят от последовательности побочных эффектов при назначении и ++ и, следовательно, вызывает UB.