|
Типы, операции и выражения
2.12. Старшинство и порядок вычисления
В приводимой ниже таблице сведены правила старшинства и ассоциативности всех операций, включая и те, которые мы еще не обсуждали. Операции, расположенные в одной строке, имеют один и тот же уровень старшинства; строки расположены в порядке убывания старшинства. Так, например, операции *, / и % имеют одинаковый уровень старшинства, который выше, чем уровень операций + и -.
OPERATOR | ASSOCIATIVITY |
---|---|
() [] -> . | LEFT TO RIGHT |
! ^ ++ -- - (TYPE) * & SIZEOF | RIGHT TO LEFT |
* / % | LEFT TO RIGHT |
+ - | LEFT TO RIGHT |
<< >> | LEFT TO RIGHT |
< <= > >= | LEFT TO RIGHT |
== != | LEFT TO RIGHT |
& | LEFT TO RIGHT |
^ | LEFT TO RIGHT |
| | LEFT TO RIGHT |
&& | LEFT TO RIGHT |
|| | LEFT TO RIGHT |
?: | RIGHT TO LEFT |
= += -= ETC. | RIGHT TO LEFT |
, (CHAPTER 3) | LEFT TO RIGHT |
Операции -> и . Используются для доступа к элементам структур ; они будут описаны в "лекции №6" вместе с sizeof (размер объекта). В "лекции №5" обсуждаются операции * (косвенная адресация ) и & ( адрес ). Отметим, что уровень старшинства побитовых логических операций &, ^ и ' ниже уровня операций == и !=. Это приводит к тому, что осуществляющие побитовую проверку выражения, подобные
if ((x & mask) == 0) ...
Для получения правильных результатов должны заключаться в круглые скобки.
Как уже отмечалось ранее, выражения, в которые входит одна из ассоциативных и коммутативных операций ( *, +, &, ^, ' ), могут перегруппировываться, даже если они заключены в круглые скобки. В большинстве случаев это не приводит к каким бы то ни было расхождениям; в ситуациях, где такие расхождения все же возможны, для обеспечения нужного порядка вычислений можно использовать явные промежуточные переменные.
В языке "C", как и в большинстве языков, не фиксируется порядок вычисления операндов в операторе. Например в операторе вида
x = f() + g();
сначала может быть вычислено f, а потом g, и наоборот; поэтому, если либо f, либо g изменяют внешнюю переменную, от которой зависит другой операнд, то значение x может зависеть от порядка вычислений. Для обеспечения нужной последовательности промежуточные результаты можно опять запоминать во временных переменных.
Подобным же образом не фиксируется порядок вычисления аргументов функции, так что оператор
printf("%d %d\n",++n,power(2,n));
может давать (и действительно дает) на разных машинах разные результаты в зависимости от того, увеличивается ли n до или после обращения к функции power. Правильным решением, конечно, является запись
++n; printf("%d %d\n",n,power(2,n));
Обращения к функциям, вложенные операции присваивания, операции увеличения и уменьшения приводят к так называемым "побочным эффектам" - некоторые переменные изменяются как побочный результат вычисления выражений. В любом выражении, в котором возникают побочные эффекты, могут существовать очень тонкие зависимости от порядка, в котором определяются входящие в него переменные. примером типичной неудачной ситуации является оператор
a[i] = i++;
Возникает вопрос, старое или новое значение i служит в качестве индекса. Компилятор может поступать разными способами и в зависимости от своей интерпретации выдавать разные результаты. Тот случай, когда происходят побочные эффекты (присваивание фактическим переменным ), - оставляется на усмотрение компилятора, так как наилучший порядок сильно зависит от архитектуры машины.
Из этих рассуждений вытекает такая мораль: написание программ, зависящих от порядка вычислений, является плохим методом программирования на любом языке. Конечно, необходимо знать, чего следует избегать, но если вы не в курсе, как некоторые вещи реализованы на разных машинах, это неведение может предохранить вас от неприятностей. (Отладочная программа lint укажет большинство мест, зависящих от порядка вычислений.