Перейти к содержимому
Topos Uranos

Присваивание, побочные эффекты и неопределённое поведение

Краткое содержание

Выражение C++ может делать две разные вещи: давать значение и изменять состояние программы. x + 1 только даёт значение; x = 7 даёт значение и, кроме того, изменяет x; x++ даёт значение, которое имела x, и оставляет в x другое. Этот урок изучает различие между значением и эффектом выражения: присваивание как выражение, составное присваивание, инкремент и декремент в префиксной и постфиксной формах и порядок, в котором вычисляются части выражения, который стандарт фиксирует лишь отчасти. Отсюда следует вопрос, которому подчинена остальная часть урока: что стандарт обещает о программе. Ответом служит каталог из четырёх ступеней, от определённого поведения до неопределённого, который собирает все случаи, встречавшиеся до сих пор, и говорит для каждого, что его обнаруживает (компилятор, санитайзер, библиотека) и чего не обнаруживает никто.

Цели обучения

  1. Отличать значение выражения от его побочного эффекта в присваиваниях, инкрементах и вызовах.
  2. Предсказывать результат x++ и ++x, используемых как значение, и выполнять вручную программу с составными присваиваниями.
  3. Распознавать выражения, которые дважды изменяют переменную или читают и изменяют её без порядка, зафиксированного стандартом, и переписывать их в виде отдельных инструкций.
  4. Относить конструкцию к определённому, определяемому реализацией, неуточнённому или неопределённому поведению, с примером каждого вида.
  5. Говорить для каждого неопределённого поведения из каталога, предупреждает ли о нём компилятор, останавливает ли его санитайзер или библиотека, или его не обнаруживает никто.

Значение и эффект

Урок Арифметические операции изучал выражение как дерево, которое сводится к значению: 2 + 3 * 4 равно 14, и его вычисление ничего не меняет вне самого выражения. Урок Отношения и логика сделал то же самое с условиями, значение которых есть true или false. Однако есть выражения, которые, помимо того что дают значение, оставляют след в состоянии программы: изменяют значение переменной, пишут на экран или читают из ввода. Этот след называется побочным эффектом, или просто эффектом, и слово «побочный» не означает, что он менее важен: в x = 7 эффект, то есть то, что x становится равной 7, и есть именно то, чего добиваются.

Если это так, каждое выражение следует читать с двумя вопросами: что оно даёт и что оно делает. Три примера закрепляют различие. count + 1 даёт число, следующее за count, и ничего не делает: count сохраняет своё значение. count = 9 даёт 9 и делает count равной 9. std::println("привет") тоже является выражением, вызовом функции, и важен в нём эффект: вывести строку. Собственные функции будут изучаться в уроке Функции, и там это различие вернётся: функцию, которая только вычисляет свой результат, легче понять и проверить, чем ту, которая, кроме того, изменяет что-то вне себя.

Инструкция вроде count + 1; является допустимым кодом C++: значение вычисляется и отбрасывается, так что пользы от неё нет, и GCC предупреждает об этом с -Wunused-value. Инструкция-выражение имеет смысл благодаря своему эффекту. Отсюда следует, что выражения с эффектом и движут программу, и требуют осторожности, потому что эффект происходит в какой-то момент, а этот момент не всегда тот, который предполагает читатель.