Новичку нужно простое объяснение разницы между порядком оценки и приоритетностью/ассоциативностью

Я читаю конец 2-й главы K & R, и у меня возникают трудности с пониманием двух конкретных не связанных между собой примеров строк кода (которые следуют) вместе с комментариями к ним в книге:

x = f() + g();
a[i] = i++;

ПЕРВАЯ ЛИНИЯ - у меня нет проблем с пониманием того, что в стандарте не указан порядок оценки для оператора +, и, следовательно, не определено, выполняет ли сначала f() или g() оценку (и что Вот почему я думаю, что вопрос не является дубликатом). Моя путаница проистекает из того факта, что если мы посмотрим на диаграмму приоритета оператора C C operator precedence chart, то в ней будут приведены вызовы функций с наивысшим приоритетом с ассоциативностью слева направо. Не значит ли это, что f() должен вызывать/оценивать до g()? Очевидно, нет, но я не знаю, чего мне не хватает.

ВТОРАЯ ЛИНИЯ - Снова похожая головоломка, касающаяся того, индексирован ли массив к начальному значению i или увеличенному значению. Однако опять-таки в диаграмме приоритетов операторов цитирование массивов имеет наивысший приоритет с ассоциативностью слева направо. Следовательно, не будет ли в первую очередь оцениваться подписка на массив, в результате чего массив будет подписан на начальное значение i и устранена какая-либо однозначность? Очевидно, нет, и я что-то упускаю.

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

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

Ответ 1

ПЕРВАЯ ЛИНИЯ

Ассоциативность слева направо означает, что выражение, такое как f()()(), оценивается как ((f())())(). Ассоциативность оператора вызова функции () ничего не говорит о его связи с другими операторами, такими как +.

(Обратите внимание, что ассоциативность действительно имеет смысл только для нестабильных инфиксных операторов, таких как бинарные +, % или ,. Для таких операторов, как вызов функции или унарные, ассоциативность в целом довольно бессмысленна.)

ВТОРАЯ ЛИНИЯ

Приоритет оператора влияет на синтаксический анализ, а не на порядок оценки. Тот факт, что [] имеет более высокий приоритет, чем =, означает, что выражение анализируется как (a[i]) = (i++). Это очень мало говорит о порядке оценки; a[i] и i++ должны быть оценены до назначения, но ничего не сказано об их порядке относительно друг друга.

Надеемся, чтобы устранить путаницу:

Ассоциативность управляет синтаксическим анализом и сообщает вам, анализируется ли a + b + c как (a + b) + c (слева направо) или как a + (b + c) (справа налево).

Приоритет также управляет синтаксическим анализом и сообщает вам, анализируется ли a + b * c как (a + b) * c (+ имеет более высокий приоритет, чем *), или как a + (b * c) (* имеет более высокий приоритет, чем +).

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

Ответ 2

  1. На самом деле не имеет смысла говорить, что вызовы функций имеют ассоциативность слева направо, и даже если бы они были значимыми, это применимо только к экзотическим комбинациям, когда два оператора вызова функции применялись рядом друг с другом. О двух отдельных вызовах функций по обе стороны от оператора + ничего не говорится.
  2. Приоритетность и ассоциативность совсем не помогают нам в выражении a[i] = i++. Просто не существует правила, которое бы точно указывало, когда внутри выражения i++ сохраняет новый результат обратно в i, что означает, что не существует правила, сообщающего нам, использует ли часть a[i] старое или новое значение. Вот почему это выражение не определено.

Приоритет говорит вам, что происходит, когда у вас есть два разных оператора, которые могут применяться. В a + b * c применяется ли вначале + или *? В *p++ применяется ли вначале * или ++? Приоритет отвечает на эти вопросы.

Ассоциативность говорит вам, что происходит, когда у вас есть два одинаковых оператора, которые могут применяться (обычно это строка с одинаковыми операторами в строке). В a + b + c, что + применяется первым? Вот что отвечает ассоциативность.

Но ответы на эти вопросы (то есть ответы, предоставляемые правилами приоритета и ассоциативности) применяются довольно узко. Они сообщают вам, какой из двух операторов, о которых вы задумывались, применяются в первую очередь, но они ничего не говорят вам о большем выражении или о меньших подвыражениях "под" операторами, о которых вы задавались вопросом. (Например, если я написал (a - b) + (c - d) * (e - f), нет правила, чтобы сказать, какое из вычитаний произойдет первым.)

Суть в том, что приоритет и ассоциативность не полностью определяют порядок оценки. Скажем так, опять немного по-другому: приоритет и ассоциативность частично определяют порядок оценки в определенных выражениях, но они не полностью определяют порядок оценки во всех выражениях.

В Си некоторые аспекты порядка оценки не определены, а некоторые не определены. (Это, в отличие от, насколько я понимаю, Java, где определены все аспекты порядка оценки.)

См. также этот ответ, который, хотя и касается другого вопроса, объясняет те же моменты более подробно.

Ответ 3

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

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

Но рассмотрим вычитание. Если вы напишите

100 - 50 - 5

имеет значение, относитесь ли вы к этому как

(100 - 50) - 5 = 45

или

100 - (50 - 5) = 55

Левая ассоциативность означает, что будет использоваться первая интерпретация.

Приоритет вступает в игру, когда у вас есть разные операторы, например,

10 * 20 + 5

Поскольку * имеет более высокий приоритет, чем +, это рассматривается как

(10 * 20) + 5 = 205

а не

10 * (20 + 5) = 250

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

x = f() - g() - h()

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

temp_h = h();
temp_f = f();
temp_g = g();
x = (temp_f - temp_g) - temp_h;

Любое изменение порядка первых трех строк будет разрешено в качестве интерпретации.

Примечание

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

x + y - z = (x + y) - z
y - z + x = (y - z) + x

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

Ответ 4

Относительно вашего первого вопроса:

x = f() + g();

Ассоциативность слева направо относится к операторам одного уровня, которые непосредственно сгруппированы вместе. Например:

x = a + b - c;

Здесь операторы + и - имеют одинаковый уровень приоритета, поэтому сначала оценивается a + b, затем a + b - c.

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

x()();

В этом случае сначала должна быть вызвана функция x, затем вызывается указатель функции, возвращенный x.

Для второго:

a[i] = i++;

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

Ответ 5

ПЕРВАЯ ЛИНИЯ - Ассоциативность здесь не актуальна. Ассоциативность действительно вступает в игру только тогда, когда у вас есть последовательность операторов с одинаковым приоритетом. Давайте возьмем выражение x + y - z. Аддитивные операторы + и - являются ассоциативными слева, поэтому последовательность анализируется как (x + y) - z - IOW, результат z вычитается из результата x + y.

ЭТО НЕ ОЗНАЧАЕТ, что какие-либо из x, y или z должны оцениваться в каком-либо конкретном порядке. Это не означает, что x + y должен оцениваться до z. Это только означает, что результат x + y должен быть известен до того, как результат z будет вычтен из него.

Что касается x = f() + g(), то все, что имеет значение, это то, что результаты f() и g() известны до того, как их можно будет сложить вместе - это не означает, что f() должен быть оценен до g(). И опять же, ассоциативность здесь не влияет.

ВТОРАЯ ЛИНИЯ - этот оператор вызывает неопределенное поведение именно потому, что порядок операций не определен (строго говоря, выражения a[i] и i++ не упорядочены по отношению друг к другу). Нельзя одновременно обновить объект (i++) и использовать его значение в вычислении (a[i]) в одном и том же выражении без промежуточной точки последовательности. Результат не будет согласованным или предсказуемым от сборки к сборке (он даже не должен быть согласованным от запуска к запуску одной и той же сборки). Такие выражения, как a[i] = i++ (или a[i++] = i) и x = x++, имеют неопределенное поведение, и результатом может быть буквально что угодно.

Обратите внимание, что операторы &&, ||, ?: и запятые действительно выполняют оценку слева направо и вводят точки последовательности, поэтому выражение выглядит как

i++ && a[i]

четко определено - сначала будет оцениваться i++, а его побочный эффект будет применен до оценки a[i].

Приоритетность и ассоциативность не соответствуют языковой грамматике - например, грамматика для аддитивных операторов + и - равна.

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

IOW, additive-expression может произвести один multiplicative-expression, или он может произвести другой additive-expression, за которым следует аддитивный оператор, за которым следует multiplicative-expression. Давайте посмотрим, как это работает с x + y - z:

x -- additive-expression ---------+
                                  |
+                                 +-- additive-expression --+
                                  |                         |
y -- multiplicative-expression ---+                         |
                                                            +-- additive-expression
-                                                           |
                                                            |
z -- multiplicative-expression -----------------------------+

Вы можете видеть, что x + y сначала группируется в additive-expression, а затем это выражение группируется с z для формирования другого additive-expression.