В приводимой ниже таблице сведены правила старшинства и ас-социативности всех операций, включая и те, которые мы еще необсуждали. Операции, расположенные в одной строке, имеютодин и тот же уровень старшинства; строки расположены в по-рядке убывания старшинства. Так, например, операции *, / и %имеют одинаковый уровень старшинства, который выше, чем уро-вень операций + и -. 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 укажет большинство мест, зависящих от порядка вы-числений.
* 3. Поток управления *
Управляющие операторы языка определяют порядок вычисле-ний. В приведенных ранее примерах мы уже встречались с наи-более употребительными управляющими конструкциями языка "C";здесь мы опишем остальные операторы управления и уточнимдействия операторов, обсуждавшихся ранее.