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

Приоритет оператора и порядок оценки

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

Возьмем простой пример:

int a=1;  // Line 1
a = a++ + ++a;  // Line 2
printf("%d",a);  // Line 3

Теперь видно, что Line 2 приводит к Undefined Поведению, так как Точки последовательности в C и С++ включают:

  • Между оценкой левого и правого операндов && (логическое И), || (логическое ИЛИ) и запятая операторы. Например, в выражение *p++ != 0 && *q++ != 0, все побочные эффекты подвыражения *p++ != 0 завершены перед любой попыткой доступа к q.

  • Между оценкой первого операнда троичного оператора "вопросительного знака" и второго или третьего операнда. Например, в выражении a = (*p++) ? (*p++) : 0 имеется точка последовательности после первый *p++, то есть он уже был увеличен к тому времени, когда выполняется второй экземпляр.

  • В конце полного выражения. Эта категория включает выражение (например, присвоение a=b;), операторы возврата, контролируя выражения if, switch, while или do-while, и все три выражения в выражении for.

  • Прежде чем функция будет введена в вызов функции. Порядок, в котором аргументы оцениваются не указанная, но эта точка последовательности означает, что все их побочные эффекты завершены до того, как функция вошел. В выражении f(i++) + g(j++) + h(k++), f вызывается с параметр исходного значения i, но i увеличивается до ввода тело f. Аналогично, j и k являются обновлен перед входом g и hсоответственно. Однако это не заданный в каком порядке f(), g(), h()выполняются и в каком порядке i, j, k увеличиваются. Значения j и k в теле f, следовательно, undefined. 3 Обратите внимание, что функция вызов f(a,b,c) не используется запятой и порядка оценка для a, b и c равна не определено.

  • При возврате функции, после того, как возвращаемое значение будет скопировано в вызывающий контекст. (Эта точка последовательности указан только в стандарте С++; он присутствует только неявно в С.)

  • В конце инициализатора; например, после оценки 5 в объявлении int a = 5;.

Таким образом, переходя по точке № 3:

В конце полного выражения. Эта категория включает в себя выражения (например, присваивание a = b;), операторы return, управляющие выражения if, switch, while или do-while и все три выражения в инструкции for.

Line 2 явно приводит к Undefined Поведению. Это показывает, как Undefined Поведение тесно связано с Точками последовательности.

Теперь возьмем еще один пример:

int x=10,y=1,z=2; // Line 4
int result = x<y<z; // Line 5

Теперь очевидно, что Line 5 сделает переменную result сохранением 1.

Теперь выражение x<y<z в Line 5 может быть оценено как:

x<(y<z) или (x<y)<z. В первом случае значение result будет 0, а во втором случае result будет 1. Но мы знаем, когда вступает в игру Operator Precedence Equal/Same - Associativity, следовательно, оценивается как (x<y)<z.

Это то, что сказано в этой статье MSDN:

Приоритет и ассоциативность операторов C влияют на группировку и оценку операндов в выражениях. Приоритет оператора имеет смысл только в том случае, если присутствуют другие операторы с более высоким или более низким приоритетом. Сначала оцениваются выражения с операторами с более высоким приоритетом. Приоритет также можно описать словом "привязка". Говорят, что операторы с более высоким приоритетом имеют более жесткую привязку.

Теперь о статье:

Он упоминает: "Выражения с операторами с более высоким приоритетом оцениваются первыми".

Это может звучать некорректно. Но, я думаю, статья не говорит что-то неправильно, если мы считаем, что () также является оператором x<y<z таким же, как (x<y)<z. Мое рассуждение состоит в том, что ассоциативность не вступает в игру, тогда полная оценка выражений станет двусмысленной, поскольку < не является Точка последовательности.

Кроме того, другая ссылка, которую я нашел, говорит об этом на Приоритет операторов и ассоциативность:

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

Итак, второй пример int result=x<y<z, мы можем видеть здесь, что во всех трех выражениях есть x, y и z, так как простейшая форма выражения состоит из одного литеральная константа или объект. Следовательно, результат выражений x, y, z будет там rvalues ​​, т.е. 10, 1 и 2 соответственно. Следовательно, теперь мы можем интерпретировать x<y<z как 10<1<2.

Теперь, не вступает ли ассоциативность в игру, так как теперь у нас есть 2 выражения для оценки: 10<1 или 1<2, и поскольку приоритет оператора тот же, они оцениваются слева направо?

Взяв последний пример в качестве аргумента:

int myval = ( printf("Operator\n"), printf("Precedence\n"), printf("vs\n"),
printf("Order of Evaluation\n") );

Теперь в приведенном выше примере, поскольку оператор comma имеет одинаковый приоритет, выражения оцениваются left-to-right, а возвращаемое значение последнего printf() сохраняется в myval.

В SO/IEC 9899: 201x в разделе J.1 Непоказанное поведение:

Порядок, в котором оцениваются подвыражения, и порядок, в котором побочные эффекты имеют место, кроме как указано для функции-вызова(), &, ||,?: и запятой операторов (6.5).

Теперь я хотел бы знать, было бы неправильно сказать:

Порядок оценки зависит от приоритета операторов, оставляя случаи Unspecified Behavior.

Я бы хотел, чтобы меня исправили, если были допущены какие-либо ошибки в чем-то, что я сказал в моем вопросе. Причина, по которой я опубликовал этот вопрос, связана с путаницей, созданной в моей голове статьей MSDN. Является ли он Ошибка или нет?

4b9b3361

Ответ 1

Да, статья MSDN ошибочна, по крайней мере, в отношении стандартных C и С++ 1.

Сказав это, позвольте мне начать с примечания о терминологии: в стандарте С++ они (в основном - несколько промахов) используют "оценку" для ссылки на оценку операнда и "вычисление значений", для обозначения операции. Итак, когда (например) вы выполняете a + b, каждый из a и b оценивается, то вычисление значения выполняется определить результат.

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

Теперь, когда порядок оценки определяется приоритетом, нет, это не так! Это так просто. Например, рассмотрим ваш пример x < y < z. Согласно правилам ассоциативности, это анализирует как (x < y) < z. Теперь рассмотрим оценку этого выражения на машине стека. Для него вполне допустимо сделать что-то вроде этого:

  push (z);//Оценивает свой аргумент и толкает значение в стеке
 напористый);
 толчок (х);
 test_less();//сравнивает TOS с TOS (1), толкает результат на стек
 test_less();
Код>

Это оценивает z до x или y, но все равно оценивает (x < y), затем сравнивает результат этого сравнения с z, как и предполагалось.

Резюме: Порядок оценки не зависит от ассоциативности.

Приоритет - это то же самое. Мы можем изменить выражение на x * y + z и по-прежнему оценивать z до x или y:

<Предварительно > <код > толчок (г); напористый); толчок (х); мул(); Добавить(); Код >

Сводка: Порядок оценки не зависит от приоритета.

Когда/если мы добавляем побочные эффекты, это не меняется. Я думаю, что это образовательный вопрос о побочных эффектах, которые выполняются отдельным потоком исполнения, с join в следующей последовательности (например, в конце выражения). Итак, что-то вроде a = b ++ + ++ c; может быть выполнено примерно так:

<Предварительно > <код > толчок (а); толчок (б); толчок (с + 1); side_effects_thread.queue(inc, b); side_effects_thread.queue(inc, c); Добавить(); назначить(); присоединиться (side_effects_thread); Код >

Это также показывает, почему кажущаяся зависимость не обязательно влияет на порядок оценки. Несмотря на то что a является целью назначения, это все еще оценивает a до, оценивая либо b, либо скод>. Также обратите внимание, что хотя я написал это как "поток" выше, это также может быть как поток пул потоков, все выполняются параллельно, поэтому вы не получаете никаких гарантий относительно заказа одного приращения по сравнению с другим.

Если аппаратное обеспечение имело прямую (и дешевую) поддержку поточно-безопасной очереди, это, вероятно, не будет использоваться в реальной реализации (и даже тогда это маловероятно). Помещение чего-то в потокобезопасную очередь обычно будет намного более накладным, чем выполнение одного приращения, поэтому трудно представить, что кто-либо когда-либо делает это на самом деле. Концептуально, однако, идея соответствует требованиям стандарта: когда вы используете операцию pre/post increment/decment, вы указываете операцию, которая произойдет когда-то после того, как эта часть выражения будет оценена, и будет завершена в следующая точка последовательности.

Изменить: хотя это не совсем потоковая передача, некоторые архитектуры допускают такое параллельное выполнение. Для нескольких примеров процессоры Intel Itanium и VLIW, такие как некоторые DSP, позволяют компилятору назначать несколько инструкций, которые будут выполняться параллельно. Большинство машин VLIW имеют определенный "пакетный" размер команды, который ограничивает количество команд, выполняемых параллельно. Itanium также использует пакеты инструкций, но указывает бит в пакете команд, чтобы сказать, что инструкции в текущем пакете могут выполняться параллельно с командами в следующем пакете. Используя такие механизмы, вы получаете инструкции, выполняемые параллельно, так же, как если бы вы использовали несколько потоков на архитектурах, с которыми большинство из нас знакомо.

Резюме: порядок оценки не зависит от кажущихся зависимостей

Любая попытка использования значения перед следующей точкой последовательности дает поведение undefined - в частности, "другой поток" (возможно) изменяет эти данные за это время, и у вас есть no способ синхронизации доступа с другим потоком. Любая попытка его использования приводит к поведению undefined.

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

  load variable [0]
приращение
хранить переменную [0]
для (int я = 1; я < 8; я ++) {   переменная нагрузки [i]   add_with_carry 0   хранить переменную [i]
}
Код>

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

Этот точный пример может быть довольно надуманным, но менее экстремальная версия (например, 64-разрядная переменная на 32-разрядной машине) на самом деле довольно распространена.

Заключение

Порядок оценки не зависит от приоритета, ассоциативности или (обязательно) от кажущихся зависимостей. Попытка использовать переменную, к которой применяется приращение/декремент pre/post в любой другой части выражения, действительно дает поведение полностью undefined. Хотя фактический крах вряд ли, вы определенно not гарантированно получите либо старое значение, либо новое - вы можете получить что-то еще полностью.


1 Я не проверял эту конкретную статью, но довольно много статей MSDN рассказывают о управляемых Microsoft С++ и/или С++/CLI, но мало или ничего не указывают, что они не применяются к стандартным C или С++. Это может привести к ложному виду, что они утверждают, что правила, которые они решили применить к их собственным языкам, действительно применяются к стандартным языкам. В этих случаях статьи не являются технически ложными - они просто не имеют ничего общего со стандартными C или С++. Если вы попытаетесь применить эти утверждения к стандартным C или С++, результат будет ложным.

Ответ 2

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

Ответ 3

Хороший способ взглянуть на это - взять дерево выражений.

Если у вас есть выражение, скажем x+y*z, вы можете переписать его в дерево выражений:

Применение правил приоритета и ассоциативности:

x + ( y * z )

После применения правил приоритета и ассоциативности вы можете спокойно забыть о них.

В виде дерева:

  x
+
    y
  *
    z

Теперь листья этого выражения x, y и z. Это означает, что вы можете оценить x, y и z в любом порядке, а также это означает, что вы можете оценить результат * и x в любом порядке.

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

Теперь точки последовательности приносят немного порядка в этот хаос. Они эффективно разрезают дерево на секции.

x + y * z, z = 10, x + y * z

после приоритета и ассоциативности

x + ( y * z ) , z = 10, x + ( y * z)

дерево:

      x
    +
        y
      *
        z
  , ------------
      z
    =
      10     
  , ------------
      x
    +
        y
      *
        z   

Верхняя часть дерева будет оцениваться до середины, а средняя - до нижней.

Ответ 4

Он упоминает: "Выражения с операторами с более высоким приоритетом оцениваются первыми".

Я просто повторю то, что сказал здесь. Что касается стандартных C и С++, то проблема связана с ошибкой. Приоритет влияет только на то, какие токены считаются операндами каждого оператора, но это никак не влияет на порядок оценки.

Таким образом, ссылка только объясняет, как Microsoft реализовала вещи, а не как сам язык работает.

Ответ 5

Приоритет не имеет ничего общего с порядком оценки и наоборот.

Приоритет описывают, как должно быть заключено нижнее сжатое выражение, когда выражение смешивает различные типы операторов. Например, умножение имеет более высокий приоритет, чем добавление, поэтому 2 + 3 x 4 эквивалентно 2 + (3 x 4), а не (2 + 3) x 4.

Правила оценки описывают порядок, в котором оценивается каждый операнд в выражении.

Возьмем пример

y = ++x || --y;   

По правилу приоритета оператора он будет заключен в скобки, поскольку (++/-- имеет более высокий приоритет, чем ||, который имеет более высокий приоритет, чем =):

y = ( (++x) || (--y) )   

Порядок оценки логического OR || гласит, что (C11 6.5.14)

|| оператор гарантирует оценку слева направо.

Это означает, что левый операнд, то есть подвыражение (x++) будет оцениваться первым. Из-за короткого замыкания; Если первый операнд сравнивается неравномерно с 0, второй операнд не оценивается, правый операнд --y не будет оцениваться, хотя он заключен в скобки выше, чем (++x) || (--y).

Ответ 6

Я думаю, что это только

a++ + ++a

epxression проблематично, потому что

a = a++ + ++a;

соответствует первой в 3., но затем в правиле 6.: полная оценка перед назначением.

Итак,

a++ + ++a

получает для a = 1 полностью оцененную:

1 + 3   // left to right, or
2 + 2   // right to left

Результат тот же = 4.

Элемент

a++ * ++a    // or
a++ == ++a

будет иметь undefined результаты. Не правда ли?