В C++ почему true && true || false && false == true?

Я хотел бы знать, знает ли кто-нибудь, как компилятор интерпретирует следующий код:

#include <iostream>
using namespace std;

int main() {
 cout << (true && true || false && false) << endl; // true
}

Это верно, потому что && имеет более высокий приоритет, чем || или потому что || является оператором короткого замыкания (другими словами, оператор короткого замыкания игнорирует все последующие выражения или только следующее выражение)?

12 ответов


Caladain имеет точно правильный ответ, но я хотел ответить на один из ваших комментариев на его ответ:

если происходит короткое замыкание оператора || и происходит короткое замыкание выполнения второго выражения&&, это означает, что оператор || был выполнен до второго оператора&&. Это подразумевает выполнение слева направо Для && и / / (не & & приоритет).

я думаю, что часть проблемы-то, что приоритет не совсем означает то, что вы думаете. Это правда, что && имеет более высокий приоритет, чем ||, и это точно объясняет поведение, которое вы видите. Рассмотрим случай с обычными арифметическими операторами: предположим, что мы имеем a * b + c * (d + e). Приоритет говорит нам, как вставить круглые скобки: сначала вокруг *, затем по +. Это дает нам (a * b) + (c * (d + e)); в вашем случае, у нас есть (1 && 1) || (infiniteLoop() && infiniteLoop()). Затем представьте, что выражения становятся деревьев. Сделать это, преобразуйте каждый оператор в узел с его двумя аргументами как дочерние:

Expression trees.

оценка этого дерева-это то, где происходит короткое замыкание. В арифметическом дереве вы можете представить себе первый стиль выполнения снизу вверх: сначала оцените DE = d + e, потом AB = a * b и CDE = c * DE, и конечный результат AB + CDE. Но обратите внимание, что вы могли бы одинаково хорошо оценили AB во-первых, тогда DE, CDE, и конечный результат; вы не можете заметить разницу. Однако, поскольку || и && короткое замыкание, они есть использовать эту самую левую оценку. Таким образом, оценить || мы сначала оценить 1 && 1. Поскольку это правда,|| короткое замыкание и игнорирует его правую ветвь-хотя,если он оценил его, ему пришлось бы оценить infiniteLoop() && infiniteLoop() первый.

если это помогает, вы можете думать о каждом узле в дереве как о вызове функции, который производит следующее представление plus(times(a,b), times(c,plus(d,e))) в первом случае и or(and(1,1), and(infiniteLoop(),infiniteLoop()) во втором случае. Короткое замыкание означает, что вы должны полностью оценить каждый левый аргумент функции or или and; если true (for or) или false (for and), затем игнорируйте аргумент правой руки.

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


Edit 1: в одном из ваших комментариев, Вы сказали

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

Да, это то, что определяет приоритет-но это не совсем верно. Это, безусловно, совершенно верно в C, но подумайте, как бы вы оценили (не-C!) выражение 0 * 5 ^ 7 в голове, где 5 ^ 7 = 57 и ^ имеет более высокий приоритет, чем *. Согласно вашему правилу ширины первого снизу вверх, нам нужно оценить 0 и 5 ^ 7 прежде чем мы может найти результат. Но вы не потрудились бы оценить 5 ^ 7; вы просто скажете: "ну, так как 0 * x = 0 для всех x, это должно быть 0", и пропустить всю правую ветку. Другими словами, Я не оценил обе стороны полностью, прежде чем оценить окончательное умножение; я закоротил. Аналогично, поскольку false && _ == false и true || _ == true любой _, нам, возможно, не нужно прикасаться к правой стороне; это то, что означает для оператора короткое замыкание. C не умножение короткого замыкания (хотя язык мог бы это сделать), но это тут короткое замыкание && и ||.

так же, как короткое замыкание 0 * 5 ^ 7 не изменяет обычные правила приоритета PEMDAS, короткое замыкание логических операторов не изменяет тот факт, что && имеет более высокий приоритет, чем ||. Это просто короткий путь. Так как мы должны выбрать некоторые сторона оператора для оценки во-первых, c обещает оценить сначала левая часть логических операторов; как только это будет сделано, есть очевидный (и полезный) способ избежать оценки правой стороны для определенных значений, и C обещает это сделать.

ваше правило-оцените ширину выражения-сначала снизу вверх-также хорошо определено,и язык может выбрать это. Однако у него есть недостаток в том, что он не допускает короткого замыкания, что является полезным поведением. Но если каждое выражение в вашем дереве четко определено (без циклов) и pure (без изменения переменных, печати и т. д.), тогда вы не можете сказать разницу. Только в этих странных случаях, которые математические определения "и" и "или" не охватывают, короткое замыкание даже видно.

кроме того, обратите внимание, что нет ничего фундаментальной о том, что короткое замыкание работает с фиксаторами левое выражение. Можно определить язык Ɔ, где ⅋⅋ представляет and и \ представляет ||, но где 0 ⅋⅋ infiniteLoop() и 1 \ infiniteLoop() будет петля, и infiniteLoop() ⅋⅋ 0 и infiniteLoop() \ 1 будет false и True, соответственно. Это просто соответствует выбору для оценки правой стороны сначала вместо левой стороны, а затем упрощая таким же образом.

в двух словах: приоритет говорит нам, как построить дерево синтаксического анализа. Единственными разумными порядками для оценки дерева разбора являются те, которые ведут себя как будто мы оцениваем его ширину снизу-вверх (как вы хотите сделать) о четко определенных чистых значениях. Для неопределенных или нечистых значений,некоторые должен быть выбран линейный порядок.1 после выбора линейного порядка некоторые значения для одной стороны оператора могут однозначно определять результат всего выражения (например, 0 * _ == _ * 0 == 0, false && _ == _ && false == false или true || _ == _ || true == true). Из-за этого, вы можете быть в состоянии уйти без завершения оценки того, что приходит после в линейный порядок; C обещает сделать это для логических операторов && и || оценивая их слева направо, и не делать этого ни для чего другого. Однако, благодаря приоритету, мы do известно, что true || true && false is true, а не false: потому что

  true || true && false
→ true || (true && false)
→ true || false
→ true

вместо

  true || true && false
↛ (true || true) && false
→ true && false
→ false

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


&& имеет более высокий приоритет, чем ||.


(true && true || false && false) оценивается с && с более высоким приоритетом.

TRUE && TRUE = True

FALSE && FALSE = False

True || False = True

обновление:

1&&1||infiniteLoop()&&infiniteLoop()

почему это дает true в C++?

как и раньше, давайте разберем его. && имеет более высокий приоритет, чем || и булевы операторы короткого замыкания в C++.

1 && 1 = True.

когда значение bool преобразуется в целочисленное значение, то

false -> 0
true -> 1

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

в среде без короткого замыкания это выражение будет висеть вечно, потому что обе стороны OR будут "оценены", а правая сторона будет висеть.

если вы путаете о приоритете, то это как вещи оценили бы в вашем исходное сообщение, если | / имеет более высокий приоритет, чем &&:

1st.) True || False = True
2nd.) True && 1st = True
3rd.) 2nd && false = false
Expression = False;

я не могу вспомнить, идет ли он справа налево или слева направо, но в любом случае результат будет одинаковым. В вашем втором посте, если | / имел более высокое precendence:

1st.) 1||InfLoop();  Hang forever, but assuming it didn't
2nd.) 1 && 1st;
3rd.) 2nd && InfLoop(); Hang Forever

tl; dr: это все еще приоритет, который заставляет &&оцениваться первым, но компилятор также короткое замыкание OR. По сути, компилятор группирует порядок операций следующим образом (упрощенное представление, put вниз вилы : - P)

1st.) Is 1&&1 True?
2nd.) Evaluate if the Left side of the operation is true, 
      if so, skip the second test and return True,
      Otherwise return the value of the second test(this is the OR)
3rd.) Is Inf() && Inf() True? (this would hang forever since 
      you have an infinite loop)

обновление #2: "Однако этот пример доказывает, что && не имеет приоритета, так как || оценивается перед вторым &&. Это показывает, что && и || имеют равный приоритет и вычисляются слева направо."

" если бы & & имел приоритет, он бы оценил первый && (1), затем второй && (бесконечные циклы) и повесил программу. Поскольку этого не происходит, & & не оценивается раньше ||."

Давайте этих деталях.

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

давайте сначала рассмотрим приоритет. Приоритет-это короткая рука для "порядка операций" по существу, учитывая это утверждение: 1 + 2 * 3 в каком порядке следует группировать операции для оценка?

математика четко определяет порядок операций как предоставление умножения более высокого приоритета, чем сложение.

1 + (2 * 3) = 1 + 2 * 3
2 * 3 is evaluated first, and then 1 is added to the result.
* has higher precedence than +, thus that operation is evaluated first.

теперь перейдем к логическим выражениям: (&&=AND, || = OR)

true AND false OR true

C++ дает и более высокий приоритет, чем OR, таким образом

(true AND false) OR true
true AND false is evaluated first, and then 
      used as the left hand for the OR statement

Итак, только по приоритету, (true & & true | / false & & false) будет работать в следующем порядке:

((true && true) || (false && false)) = (true && true || false && false)
1st Comparison.) true && true
2nd Comparison.) false && false
3rd Comparison.) Result of 1st comparison || Result of Second

со мной до сих пор? Теперь давайте в короткое замыкание: В C++ логические операторы называются "короткими замыканиями". Это означает, что компилятор будет смотреть на данное заявление, а выбрать "лучший путь" для оценки. Возьмем такой пример:

(true && true) || (false && false)
There is no need to evaluate the (false && false) if (true && true) 
equals true, since only one side of the OR statement needs to be true.
Thus, the compiler will Short Circuit the expression.  Here's the compiler's
Simplified logic:
1st.) Is (true && true) True?
2nd.) Evaluate if the Left side of the operation is true, 
      if so, skip the second test and return True,
      Otherwise return the value of the second test(this is the OR)
3rd.) Is (false && false) True? Return this value

как вы можете видеть, если (true && true) оценивается TRUE, то нет необходимости тратить такты, оценивая, если (false && false) истинно.

C++ всегда короткие Circuts, но другие языки обеспечивают механизмы для того, что называется " нетерпеливый" операторы.

возьмите, например, язык программирования Ada. В аду, "И" И "ИЛИ", "охотно" операторы..они заставляют все оценивать.

в Ada (true и true) или (false и false) будет оценивать как (true и true), так и (false и false) перед оценкой OR. Ada также дает вам возможность короткого замыкания, а затем и или иначе, что даст вам то же поведение, что и C++.

я надеюсь, что полностью ответил на ваш вопрос. Если нет, дайте мне знать: -)

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

" если происходит короткое замыкание оператора || и происходит короткое замыкание выполнения второго выражения&&, это означает, что оператор || был выполнен до второго оператора&&. Это подразумевает выполнение слева направо Для && и / / (не & & приоритет)."

давайте посмотрим на это пример:

(false && infLoop()) || (true && true) = true (Put a breakpoint in InfLoop and it won't get hit)
false && infLoop() || true && true = true  (Put a breakpoint in InfLoop and it won't get hit)
false || (false && true && infLoop()) || true = false (infLoop doesn't get hit)

если то, что вы говорили, было правдой, InfLoop получил бы удар в первых двух. Вы также заметите, что InfLoop () также не вызывается в третьем примере.

теперь, давайте посмотрим на это:

(false || true && infLoop() || true);

Infloop получает вызов! Если или имеет более высокую точность, чем &&, то компилятор будет оценивать:

(false || true) && (infLoop() || true) = true;
(false || true) =true
(infLoop() || true = true (infLoop isn't called)

но InfLoop называется! Вот почему:--19-->

(false || true && infLoop() || true);
1st Comparison.) true && InfLoop() (InfLoop gets called)
2nd Comparison.) False || 1st Comp (will never get here)
3rd Comparison.) 2nd Comp || true; (will never get here)

Precendece только устанавливает группировку оперативный. При этом && больше, чем ||.

true && false || true && true gets grouped as
(true && false) || (true && true);

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

Consider: false && infLoop() || true && true
Precedence Grouping goes like this:
(false && infLoop()) || (true && true)
The compiler then looks at it, and decides it will order the execution in this order:
(true && true) THEN || THEN (false && InfLoop())

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


два факта объясняют поведение обоих примерах. Во-первых, приоритет && выше ||. Во-вторых, оба логических оператора используют оценку короткого замыкания.

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

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

Итак, предполагая обычные определения TRUE и FALSE, TRUE && TRUE || FALSE && FALSE оценивается, начиная с левой стороны. Первый TRUE не заставляет результат первого &&, Так что TRUE вычисляется, а затем выражение TRUE && TRUE оценивается (до TRUE). Теперь || знает его LHS. Еще лучше, его LHS заставил результат || чтобы быть известным, поэтому он пропускает оценка всей его RHS.

точно такой же порядок оценки применяется во втором случае. Начиная с RHS || не имеет значения, он не оценивается и не вызывает infiniteLoop() сделано.

это поведение по дизайну и полезно. Например, вы можете написать p && p->next зная, что выражение никогда не будет пытаться разыменовать нулевой указатель.


&& действительно более высокий приоритет.


" если происходит короткое замыкание оператора || и происходит короткое замыкание выполнения второго выражения&&, это означает, что оператор || был выполнен до второго оператора&&. Это подразумевает выполнение слева направо Для && и / / (не & & приоритет)."

не совсем.

(xx && yy || zz && qq)

будет оцениваться следующим образом:

  1. Проверьте первый оператор.
  2. оценить xx && yy
  3. проверьте следующие оператор.
  4. если следующий оператор ||, и первая часть утверждения истинна, пропустите остальное.
  5. в противном случае проверьте наличие следующего оператора после ||, и оценить это: zz && qq
  6. и, наконец, оценить ||.

из моего понимания, C++ разработан так, что он читает вещи, прежде чем он начнет оценивать. Ведь в нашем примере, он не знает, что у нас есть второй && регистрация после || пока он читает это означает, что он должен читать в || прежде чем он доберется до второй &&. Таким образом, если первая часть оценивается как true, она не будет делать часть после ||, но если первая часть оценивается как false, то она будет делать первую часть, читайте в ||, найдите и оцените вторую часть и используйте результат второй части для определения конечного результата.


Что касается вашего редактирования: infiniteLoop () не будет оцениваться, потому что true || (что угодно) всегда истинно. Используйте true | (whatever), если что-то должно быть выполнено.


о true && true || infiniteLoop() && infiniteLoop() пример, ни один из бесконечных вызовов цикла не оценивается из-за двух характеристик, Объединенных: && имеет приоритет над | | и || короткими замыканиями, когда левая сторона истинна.

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

((( true && true ) || infiniteLoop ) && infiniteLoop )
(( true || infiniteLoop ) && infiniteLoop )
=> first call to infiniteLoop is short-circuited
(true && infiniteLoop) => second call to infiniteLoop would have to be evaluated

но из-за приоритета&&, оценка фактически идет

(( true && true ) || ( infiniteLoop && infiniteLoop ))
( true || ( infiniteLoop && infiniteLoop ))
=> the entire ( infiniteLoop && infiniteLoop ) expression is short circuited
( true )

относительно последнего кода Эндрю,

#include <iostream>
using namespace std;

bool infiniteLoop () {
    while (true);
    return false;
}

int main() {
    cout << (true && true || infiniteLoop() && infiniteLoop()) << endl; // true
}

оценка короткого замыкания означает, что вызовы infiniteLoop гарантированно не будут выполнены.

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

Итак, с компилятором, который находится на "переднем крае" C++0x-conformance, программа может завершить работу с некоторые результат, даже если он выполнен вызов infiniteLoop ! Конечно, с таким компилятором он также мог бы произвести это ужасное явление, носовые демоны...

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

Cheers & hth.,


поскольку и / или / true / false очень похожи на */+/1/0 (они математически эквивалентны-иш), верно также следующее:

1 * 1 + 0 * 0 == 1

и довольно легко запомнить...

Так что да, это связано с приоритетом и короткое замыкание. Precendence логических ops довольно легко, если вы сопоставите его с соответствующими целочисленными ops.


это последовательная иллюстрация:

  (true && true || false && false)
= ((true && true) || (false && false))  // because && is higher precedence than ||, 
                                        //   like 1 + 2 * 3 = 7  (* higher than +)
= ((true) || (false))
= true

но также обратите внимание, что если это

(true || ( ... ))

тогда правая сторона не оценивается, поэтому никакая функция там не вызывается, и выражение просто вернет true.


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