Кто определяет приоритет оператора C и ассоциативность?

Вступление

В каждом учебнике по C / C ++ вы найдете таблицу приоритетов операторов и ассоциативности, например:

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

http://en.cppreference.com/w/cpp/language/operator_precedence

Один из вопросов о StackOverflow задавался примерно так:

В каком порядке выполняются следующие функции:

f1() * f2() + f3();
f1() + f2() * f3();

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

f1 () -> f2 () -> f3 ()

После оценки функций вы завершаете оценку следующим образом:

(a1 * a2) + a3
а1 + (а2 * а3)

К моему удивлению, многие люди говорили мне, что я совершенно не прав. Решив доказать, что они не правы, я решил обратиться к стандарту ANSI C11. Я был еще раз удивлен, обнаружив, что очень мало упоминается о приоритетности операторов и ассоциативности.

Вопросы

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

26

Решение

Приоритет оператора определяется в соответствующем стандарте. Стандарты для C и C ++ — это одно истинное определение того, что такое C и C ++. Так что, если вы посмотрите внимательно, детали есть. На самом деле, детали в грамматика языка. Например, посмотрите на правило производства грамматики для + а также - в C ++ (вместе, Аддитивные-выражения):

additive-expression:
multiplicative-expression
additive-expression + multiplicative-expression
additive-expression - multiplicative-expression

Как видите, мультипликативный выражение является подчиненным Добавка-выражение. Это означает, что если у вас есть что-то вроде x + y * z, y * z выражение является подвыражением x + y * z, Это определяет старшинство между этими двумя операторами.

Мы также можем видеть, что левый операнд Добавка-выражение расширяется к другому Добавка-выражение, что означает, что с x + y + z, x + y является подвыражением этого. Это определяет ассоциативность.

Ассоциативность определяет, как будут сгруппированы смежные применения одного и того же оператора. Например, + является ассоциативным слева направо, что означает, что x + y + z будет сгруппирован так: (x + y) + z,

Не принимайте это за порядок оценки. Нет абсолютно никаких причин, почему значение z не может быть вычислено раньше x + y является. Важно то, что это x + y это вычисляется, а не y + z,

Для оператора вызова функции ассоциативность слева направо означает, что f()() (что может произойти, если f возвращает указатель на функцию, например) сгруппирован так: (f())() (конечно, другое направление не имело бы никакого смысла).

Теперь давайте рассмотрим пример, на который вы смотрели:

f1() + f2() * f3()

* оператор имеет более высокий приоритет, чем + оператор, поэтому выражения сгруппированы так:

f1() + (f2() * f3())

Мы даже не должны здесь рассматривать ассоциативность, потому что у нас нет одного и того же оператора рядом друг с другом.

Оценка выражений вызова функций, однако, совершенно не упорядочена. Нет никаких причин f3 не может быть вызван первым, затем f1, а потом f2, Единственным требованием в этом случае является то, что операнды оператора оцениваются раньше, чем оператор. Так что это будет означать f2 а также f3 должны быть вызваны до * оценивается и * должны быть оценены и f1 должен быть вызван до + оценивается.

Однако некоторые операторы накладывают последовательность на оценку своих операндов. Например, в x || y, x всегда оценивается раньше y, Это позволяет для короткого замыкания, где y не нужно оценивать, если x как известно, уже true,

Порядок оценки был ранее определен в C и C ++ с использованием последовательность точек, и оба изменили терминологию, чтобы определить вещи с точки зрения последовательность перед отношения. Для получения дополнительной информации см. Неопределенные Точки Поведения и Последовательности.

42

Другие решения

Приоритет операторов в стандарте C указывается синтаксисом.

(C99, 6.5p3) «Группировка операторов и операндов указывается синтаксисом. 74)»

74) «Синтаксис определяет приоритет операторов при вычислении выражения»

Обоснование C99 также говорит

«Правила приоритета закодированы в синтаксических правилах для каждого оператора».

а также

«Правила ассоциативности аналогично кодируются в синтаксических правилах».

Также обратите внимание, что ассоциативность не имеет ничего общего с порядком оценки. В:

f1 () * f2 () + f3 ()

вызовы функций оцениваются в любом порядке. Синтаксические правила Си говорят, что f1() * f2() + f3() средства (f1() * f2()) + f3() но порядок вычисления операндов в выражении не указан.

10

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

a = f1() * f2() + f3();

не допускается, так как имеет 5 операторов: 3 вызова функций, умножение и сложение. На этом упрощенном языке вы должны будете назначить все для временных фигур, а затем объединить их:

temp1 = f1();
temp2 = f2();
temp3 = temp1 * temp2;
temp4 = f3();
a = temp3 + temp4;

Ассоциативность и приоритет определяют, что последние два оператора должны выполняться в этом порядке, поскольку умножение имеет более высокий приоритет, чем сложение. Но это не определяет относительный порядок первых 3 утверждений; было бы так же правильно сделать:

temp4 = f3();
temp2 = f2();
temp1 = f1();
temp3 = temp1 * temp2;
a = temp3 + temp4;

sftrabbit привел пример, в котором актуальна ассоциативность операторов вызова функций:

a = f()();

При упрощении, как указано выше, это становится:

temp = f();
a = temp();
8

Приоритет и ассоциативность определены в стандарте, и они решают, как построить синтаксическое дерево. Приоритет работает по типу оператора (1+2*3 является 1+(2*3) и не (1+2)*3) и ассоциативность работает по позиции оператора (1+2+3 является (1+2)+3 и не 1+(2+3)).

Порядок оценки другой — он не определяет, как построить синтаксическое дерево — он определяет, как evaluate узлы операторов в синтаксическом дереве. Порядок оценки определен, чтобы не быть определенным — вы никогда не можете положиться на него, потому что компиляторы свободны выбирать любой порядок, который они считают подходящим. Это сделано для того, чтобы компиляторы могли попытаться оптимизировать код. Идея состоит в том, что программисты пишут код, который не должен зависеть от порядка вычисления, и дают одинаковые результаты независимо от порядка.

4

Ассоциативность слева направо означает, что f() - g() - h() средства (f() - g()) - h(), ничего более. предполагать f возвращается 1, предполагать g возвращается 2, предполагать h возвращается 3, Ассоциативность слева направо означает, что результат (1 - 2) - 3, или же -4: компилятору все еще разрешено первый вызов g а также h, что не имеет ничего общего с ассоциативностью, но не может дать результат 1 - (2 - 3), что было бы что-то совершенно другое.

3