netlib.narod.ru< Назад | Оглавление | Далее >

2.12. Приоритет и очередность вычислений

В таблице 2.1 показаны приоритеты и очередность выполнения всех операторов, включая и те, которые мы еще не рассматривали. Операторы, перечисленные в одной строке таблицы, имеют одинаковый приоритет, а сами строки упорядочены по убыванию приоритетов. Например, бинарные операторы *, / и % имеют одинаковый приоритет, котроый выше, чем приоритет бинарных операторов + и -. «Оператор» () означает вызов функции. Операторы -> и . (точка) используются для доступа к элементам структур; о них пойдет речь в главе 6, там же будет рассмотрен и оператор sizeof (размер объекта). Унарные операторы * (косвенное обращение к объекту по указателю) и & (получение указателя на объект) обсуждаются в главе 5. Оператор , (запятая) будет рассмотрен в главе 3.


Таблица 2.1. Приоритет операторов и очередность их выполнения


Операторы Порядок выполнения
()  []  ->  . слева направо
!  ~  ++  --
унарные +  -  *  &
(тип)  sizeof
справа налево
бинарные *  /  % слева направо
бинарные +  - слева направо
<<  >> слева направо
<  <=  >  >= слева направо
==  != слева направо
бинарный & слева направо
^ слева направо
| слева направо
&& слева направо
|| слева направо
? : справа налево
=  +=  -=  *=  /=  %=  &=  ^=  |=  <<=  >>= справа налево
, слева направо

Заметим, что приоритеты поразрядных операторов &, ^ и | ниже, чем приоритет операторов сравнения == и !=, из-за чего в проверках, таких как

    if((x & MASK) == 0) . . .

для получения правильного результата необходимо использовать скобки.

Си подобно многим языкам не фиксирует очередность вычисления операндов (за исключением операторов &&, ||, ? : и ,). Например, в инструкции вида

    x = 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 индексируется массив a — старым или измененным? Разные компиляторы могут по-разному генерировать код, что проявится в интерпретации данной записи. Стандарт сознательно устроен так, что большинство подобных вопросов оставлено на усмотрение разработчиков компилятора, так как лучший порядок вычислений определяется архитектурой машины. Стандартом только гарантируется, что все побочные эффекты при вычислении аргументов проявятся перед входом в функцию. Правда, в примере с printf это нам не помогло.

Мораль такова: писать программы, зависящие от очередности вычислений, — плохая практика, какой бы язык вы не использовали. Естественно, надо знать, чего следует избегать, но если вы не знаете, как образуются побочные эффекты на разных машинах, то лучше и не рассчитывать выиграть на особенностях частной реализации.


netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz