Кто определяет приоритет и ассоциативность оператора C?
Введение
В каждом учебнике на C/С++ вы найдете таблицу приоритетов и ассоциативности операторов, такую как:
![Operator Precedence And Associativity Table]()
http://en.cppreference.com/w/cpp/language/operator_precedence
Один из вопросов в StackOverflow спросил что-то вроде этого:
В каком порядке выполняются следующие функции:
f1() * f2() + f3();
f1() + f2() * f3();
Ссылаясь на предыдущий график, я уверенно ответил, что функции имеют лево-правовую ассоциативность, поэтому в предыдущих утверждениях они оцениваются как в обоих случаях:
f1() → f2() → f3()
После оценки функций вы заканчиваете оценку следующим образом:
(a1 * a2) + a3
a1 + (a2 * a3)
К моему удивлению, многие люди сказали мне, что я ошибался. Будучи преисполнен решимости доказать их неправоту, я решил обратиться к стандарту ANSI C11. Я снова был удивлен, узнав, что очень мало упоминается о приоритете и ассоциативности операторов.
Вопросы
- Если моя вера в то, что функции всегда оцениваются слева направо, неверна, что действительно означает таблица, ссылающаяся на приоритет функции и ассоциативность?
- Кто определяет приоритет и ассоциативность операторов, если это не ANSI? Если это ANSI, который делает определение, почему мало упоминается о приоритете и ассоциативности операторов? Является ли приоритет и ассоциативность операторов выведенными из стандарта ANSI C или определен в математике?
Ответы
Ответ 1
Приоритет оператора определяется в соответствующем стандарте. Стандарты для 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 и С++ с использованием точек последовательности, и оба изменили терминологию, чтобы определить вещи в терминах упорядоченной до отношения. Для получения дополнительной информации см. Undefined Поведение и точки последовательности.
Ответ 2
Приоритет операторов в стандарте C обозначается синтаксисом.
(C99, 6.5p3) "Группировка операторов и операндов обозначается синтаксисом. 74)"
74) "Синтаксис определяет приоритет операторов при оценке выражения"
C99 Обоснование также говорит
"Правила приоритета закодированы в синтаксические правила для каждого оператора".
и
"Правила ассоциативности аналогичным образом кодируются в синтаксические правила".
Также обратите внимание, что ассоциативность не имеет ничего общего с порядком оценки. В:
f1() * f2() + f3()
вызовы функций оцениваются в любом порядке. Синтаксические правила C говорят, что f1() * f2() + f3()
означает (f1() * f2()) + f3()
, но порядок оценки операндов в выражении не указан.
Ответ 3
Один из способов думать о приоритетности и ассоциативности - представить, что язык допускает только инструкции, содержащие назначение и один оператор, а не несколько операторов. Итак, утверждение вроде:
a = f1() * f2() + f3();
не будет разрешено, так как он имеет 5 операторов: 3 вызова функций, умножение и добавление. На этом упрощенном языке вам придется назначать все временным, а затем объединять их:
temp1 = f1();
temp2 = f2();
temp3 = temp1 * temp2;
temp4 = f3();
a = temp3 + temp4;
Ассоциативность и приоритет указывают, что последние два оператора должны выполняться в этом порядке, поскольку умножение имеет более высокий приоритет, чем добавление. Но он не указывает относительный порядок первых трех операторов; это будет так же справедливо:
temp4 = f3();
temp2 = f2();
temp1 = f1();
temp3 = temp1 * temp2;
a = temp3 + temp4;
sftrabbit привел пример, где важна ассоциативность операторов вызова функций:
a = f()();
При упрощении, как указано выше, это будет:
temp = f();
a = temp();
Ответ 4
Приоритет и ассоциативность определены в стандарте, и они решают, как построить дерево синтаксиса. Приоритет работает по типу оператора (1+2*3
is 1+(2*3)
, а не (1+2)*3
), а ассоциативность работает по позиции оператора (1+2+3
- (1+2)+3
, а не 1+(2+3)
).
Порядок оценки отличается - он не определяет, как построить дерево синтаксиса - он определяет, как evaluate
узлы операторов в дереве синтаксиса. Порядок оценки определен не для определения - вы никогда не сможете полагаться на него, потому что компиляторы могут выбирать любой порядок, который им подходит. Это делается для того, чтобы компиляторы могли попытаться оптимизировать код. Идея заключается в том, что программисты пишут код, который не должен влиять на порядок оценки, и давать те же результаты независимо от порядка.
Ответ 5
Влево-вправо ассоциативность означает, что f() - g() - h()
означает (f() - g()) - h()
, не более того. Предположим, что f
возвращает 1
. Предположим, что g
возвращает 2
. Предположим, что h
возвращает 3
. Влево-вправо ассоциативность означает, что результат равен (1 - 2) - 3
или -4
: компилятору по-прежнему разрешено сначала вызывать g
и h
, что не имеет ничего общего с ассоциативностью, но не разрешено давать результат 1 - (2 - 3)
, что было бы совершенно иным.