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

Кто определяет приоритет и ассоциативность оператора 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 или определен в математике?
4b9b3361

Ответ 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), что было бы совершенно иным.