Почему «++ x || ++ y && ++ z» сначала вычисляет «++ x», хотя оператор «&&» имеет более высокий приоритет, чем «||» - PullRequest
12 голосов
/ 13 сентября 2010

Почему ++x || ++y && ++z сначала вычисляет ++x, хотя приоритет оператора && выше ||?

Ответы [ 11 ]

42 голосов
/ 13 сентября 2010

Да?

Если вы говорите, что && связывает сильнее, чем || (, что верно ), выражение тогда эквивалентно

++x || (++y && ++z)

Поскольку || короткое замыкание , необходимо сначала оценить левую сторону.

Если вы имеете в виду, что оно должно быть эквивалентно

(++x || ++y) && ++z

То же самое по-прежнему верно, поскольку && также вызывает короткое замыкание, что означает, что || должен быть оценен первым, что, в свою очередь, делает ++x первой оценкой.

33 голосов
/ 13 сентября 2010

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

++x || (++y && ++z)

Таким образом, прежде чем программа даже начнет оценивать результат &&, она должнаоцените ++x, чтобы определить, следует ли даже оценивать правый операнд || (логические двоичные операторы являются "короткими замыканиями", что означает, что они не оценивают правую часть, если левой части достаточно дляопределить результат).Здесь нет ничего подозрительного или специфичного для компилятора.Поведение этого выражения точно определено стандартом C и будет одинаковым для любого компилятора.Это даже сработало бы, если бы x, y и z были одной и той же переменной, поскольку || и && вводят точки последовательности.

24 голосов
/ 13 сентября 2010

Размотайте , R и другие объяснили, что на самом деле происходит.Итак, позвольте мне добавить:

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

Например, рассмотрим a=b+c+d*e;* имеет более высокий приоритет, чем +, но это не означает, что d*e должен быть оценен до b+c.Это просто означает, что он должен быть оценен, прежде чем мы добавим продукт к выражению в целом.Компилятор может оценить это выражение как temp1=d*e, temp2=b+c, a=temp1+temp2 или может вычислить temp1=b+c, temp2=d*e, a=temp1+temp2.Оба будут одинаково действительны.

При коротком замыкании || и && существуют некоторые дополнительные ограничения на порядок оценки.


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

Я иногда полагаюсь на короткое замыкание, предотвращающее побочные эффекты.Как

if (!eof() && readNextInt()>0) 

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

if (confirmDelete==YES && deleteEntry()!=-1) 

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

if (customerType==RETAIL || lastDepositAmount()>100.00)

Если у lastDepositAmount() был побочный эффект, то, если customerType в рознице, этот побочный эффект никогда не произойдет.Я не думаю, что это будет очевидно для читателя.(Отчасти потому, что имя функции подразумевает, что она извлекает данные и не выполняет никаких обновлений, а отчасти потому, что нет очевидной связи между типом клиента и суммой депозита - это звучит как две независимые вещи.) Правда, этосубъективны.Но если вы сомневаетесь, выбирайте простоту и ясность, а не тривиальное улучшение производительности.Всегда выбирайте простоту и ясность вместо «эй, это круто, использование неясной функции, любой, кто читает это, будет поражен тем, насколько я должен быть умным, чтобы достаточно хорошо понимать язык, чтобы делать это» .

16 голосов
/ 13 сентября 2010

Здесь есть ужасно неправильные ответы.

Порядок вычисления этого выражения не зависит от реализации .Он четко определен!

Поскольку && связывается выше ||, это выражение эквивалентно ++x || (++y && ++z).Кроме того, и &&, и || имеют короткое замыкание , поэтому, если левой части выражения достаточно для определения значения, правая часть равна , никогда оценивается.

Когда это так?Хорошо, мы знаем, что False && a всегда разрешается в False, независимо от значения a, в частности, даже если a не одно значение, а вместо этого сложное выражение.Поэтому мы вообще не оцениваем a.Аналогично, True || a всегда разрешается в True.

. В этом примере это приводит к очень определенному порядку оценки: first ++x, затем (если вообще) ++y и , то (еще раз: если вообще) ++z.

6 голосов
/ 13 сентября 2010

Операторы оцениваются с использованием структуры данных польского дерева, как на диаграмме, и это алгоритм оценки в глубину.Давайте дадим имена операциям: A = ++ x B = ++ y C = ++ z D = B && C E = C ||D Порядок оценки: A, B, C, D, E. Конечно, C имеет оптимизацию, и если A истинно, то E будет оцениваться как истинное без оценки BC и D. Аналогично, если B ложно, C выигралне будет оцениваться для оптимизации скорости оценки.

alt text

2 голосов
/ 13 сентября 2010

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

++x || (++y && ++z)

IOW, это означает, что выражения, имеющие OR, будут ++x и (++y && ++z). не означает, что (++y && ++z) будет оценено до ++x.

Для логических операторов || и && оценка равна всегда слева направо ( онлайн-стандарт C , разделы 6.5.13 и 6.5.14).Для любого выражения

a || b

a будет полностью оценено;b не будет оцениваться, если результат a не равен 0 (представьте, что a означает ++x и b означает ++y && ++z).Аналогично, для любого выражения

a && b 

a будет полностью оценено;b не будет оцениваться, если результат a не равен 0.

Так что для вашего случая, выражение ++x вычисляется первым.Если результат только 0, , тогда будет ++y && ++z.

|| и && отличаются тем, что порядок оценки гарантированно слева направо.Это не относится к побитовым или арифметическим операторам.Например, в выражении

++x + ++y * ++z

выражения ++x, ++y и ++z могут оцениваться в любом порядке ;все приоритеты говорят вам, что результат (y+1) * (z+1) будет добавлен к результату x+1.

2 голосов
/ 13 сентября 2010

++ имеет наивысший приоритет из всех, поскольку его pre увеличивается.Это означает, что значение результата этой операции передается бинарным операторам.Однако, интересная вещь - это короткое замыкание ++ x или с остальными.Если ++ x имеет значение true, тогда все остальное может быть не сделано вообще.

1 голос
/ 13 сентября 2010

Лучшие два ответа объясняют это довольно хорошо ... Я бы отметил, что ||это "OrElse".

Правая сторона не трогается, если левая сторона верна.Левая сторона && (AndAlso) не трогается, если правая сторона ложна.

Если вы хотите, чтобы обе стороны были увеличены, используйте |и &.

http://msdn.microsoft.com/en-us/library/2h9cz2eb(v=VS.80).aspx

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

В MSDN есть таблицы, которые показывают, как части «(не оцениваются)».

Зачем касаться части 2, если часть 1 предварительно определяет результат детерминистически?

0 голосов
/ 13 сентября 2010

Нет, нет, нет

if (++x || ++y && ++z) { /* ... */ }

Да

++x;
++y;
++z;
if (x || (y && z)) { /* ... */ }

Изменить после жалоб со стороны сверстников:)

ДА!

if (is_data_valid(x, y, z)) process_data(&x, &y, &z);
0 голосов
/ 13 сентября 2010

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

В любом случае определение b ++ x означает, что значение x должно увеличиваться ДО того, как оно будет использовано, и поэтому можно ожидать, что ему при выполнении будет дан высокий приоритет.

...