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 укажет большинство мест, зависящих от порядка вычислений.
ПРЕДЫДУЩАЯ ЧАСТЬ 2.11. Условные выражения |
СОДЕРЖАНИЕ | СЛЕДУЮЩАЯ ЧАСТЬ 3. Поток управления |