Новичку нужно простое объяснение разницы между порядком оценки и приоритетностью/ассоциативностью
Я читаю конец 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
- На самом деле не имеет смысла говорить, что вызовы функций имеют ассоциативность слева направо, и даже если бы они были значимыми, это применимо только к экзотическим комбинациям, когда два оператора вызова функции применялись рядом друг с другом. О двух отдельных вызовах функций по обе стороны от оператора
+
ничего не говорится.
- Приоритетность и ассоциативность совсем не помогают нам в выражении
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
.