|
|
Типы, операции и выражения
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 укажет большинство мест, зависящих от порядка вычислений.
