Как приоритет операторов C ломает ваш код (и как это исправить)

13

Вы пишете то, что имеете в виду. Компилятор читает то, что хочет.

Если вы когда-либо смотрели на строку кода на C, будучи уверенным, что она делает одно, а затем наблюдали, как она делает совершенно другое, вы встретились с приоритетом операторов. Это не ошибка. Это особенность языка, которая кусает даже опытных разработчиков.

Возьмем эту простую арифметику:

x=5+3*6;

В школе математики говорят: сначала сложение. В C говорят: нет. Умножение и деление находятся на более высоком уровне, чем сложение и вычитание. Компилятор сначала умножает 3 на 6. Затем прибавляет 5. X получает 23. Не 48. Вы только что потеряли баллы на тесте. Или, что хуже, на продакшен-развертывании.

Заблуждение углубляется, когда вы смешиваете операторы с синтаксисом.

Посмотрите на это объявление:

char *a[10];

Является ли a указателем на массив из 10 символов? Или это массив из 10 указателей на символы?

Не зная конкретных правил приоритета, вы гадаете. В C квадратные скобки [] связывают строже, чем указатели *. Таким образом, это массив из 10 указателей на символы.

Это имеет значение, потому что C не разбирается как английский. Он разбирается как жесткая иерархия.

Вы можете написать код, который выглядит правильно, но терпит неудачу молча из-за приоритета.

Рассмотрим арифметику указателей, смешанную с доступом к структурам.

*p.i = 10;

Это не работает так, как ожидается. Оператор точка . имеет более высокий приоритет, чем оператор разыменования *. Компилятор пытается получить доступ к члену i объекта, на который указывает p, до разыменования p. Но p — это указатель, а не структура. Компилятор спотыкается. Или, что хуже, он интерпретирует память таким образом, что приводит к повреждению данных.

Вам нужно настоять на своем.

(*p).i = 10;

Скобки побеждают. Всегда.

Иерархия операторов

Керниган и Ритчи, архитекторы C, изложили приоритет в своем классическом тексте. В таблице ниже показан порядок. Верхняя строка — наивысший приоритет. Нижняя — наименьший.

  1. Скобки () и Индексация []
    • Слева направо.
  2. Унарные операторы ++, --, +, -, !, ~, (type), * (разыменование), & (адрес), sizeof
    • Справа налево.
  3. Мультипликативные *, /, %
    • Слева направо.
  4. Аддитивные +, -
    • Слева направо.
  5. Сдвиг <<, >>
    • Слева направо.
  6. Отношения <, <=, >, >=
    • Слева направо.
  7. Равенство ==, !=
    • Слева направо.
  8. Побитовое И &
    • Слева направо.
  9. Побитовое исключающее ИЛИ ^