Область действия переменной — часть программы, в которой объект существует. Отвечает на вопрос «где объект есть», а не «где его можно назвать по имени» — второе называется областью видимости.

Не из лекции

Английского оригинала в материалах нет. Ближайшие понятия стандарта — lifetime (время жизни объекта) и storage duration (длительность хранения). В части русской литературы «областью действия» называют как раз scope — при чтении других источников стоит проверять, что имеется в виду.

Зачем нужно

У переменной есть не только тип и значение. Важно ещё, где она существует и где к ней можно обратиться по имени. На маленьких программах это кажется очевидным, но как только появляются вложенные блоки, одинаковые имена и глобальные переменные, без этих правил код превращается в угадайку.

Как работает

Автоматические переменные

Обычная локальная переменная, объявленная внутри функции, действует с места объявления до ближайшего конца блока, в котором объявлена. Такие переменные называются автоматическими:

int main() {
  int x;
  {
    float y;
    {
      bool b;
      {
        char c;
      }  // здесь заканчивается действие c
    }  // здесь заканчивается действие b
  }  // здесь заканчивается действие y
}  // здесь заканчивается действие x

«Автоматическая» — потому что программист не пишет отдельную команду освобождения памяти. Объект появляется при входе в нужную часть программы и исчезает при выходе из неё.

Блок — составной оператор в фигурных скобках, см. Выражения и операторы. Именно он задаёт границы: после закрывающей скобки переменной уже нет.

int main() {
  int x = 1;
 
  {
    int y = 2;
    std::cout << x + 3 * y << '\n';
  }  // здесь y уже уничтожена
 
  return y;  // ошибка: 'y' was not declared in this scope
}

Глобальные переменные

Переменная, объявленная вне функций, называется глобальной. Она существует всё время работы программы:

int x = 1;
 
int main() {
  int y = 2;
  std::cout << x + 3 * y << '\n';
  return 0;
}

Но существовать всё время работы — не то же самое, что быть доступной отовсюду. Имя должно быть объявлено до использования: глобальная переменная, определённая ниже main, внутри main не видна, пока её не объявить через extern — см. Объявление и определение.

Переменные в заголовках циклов и условий

Имя можно объявить прямо в if, while или for. У for область действия сущности, объявленной в init, совпадает с областью самого оператора for — после цикла счётчика уже нет. Подробнее — Циклы.

Тонкости

  • Область видимости не бывает больше области действия: нельзя увидеть по имени объект, который уже не существует. Меньше — бывает: глобальная x существует, пока выполняется main, но её имя может скрыть локальная x.

  • Блок можно поставить просто так, без if и цикла, чтобы ограничить время жизни временной переменной:

    int answer = 0;
    {
      int temporary = 40 + 2;
      answer = temporary;
    }
    // temporary здесь уже нет

    Чем меньше часть программы, в которой видна переменная, тем проще понять, где её могут изменить.

Частые ошибки

  • Объявить переменную внутри блока if или цикла и попытаться прочитать её после блока.
  • Считать, что раз глобальная переменная существует всю программу, к ней можно обратиться из любой строки файла.

Углубление — L03

L03 называет области памяти: глобальная (статическая), автоматическая (стековая), динамическая и потоковая (её в курсе не разбирают). Глобальные и автоматические переменные из этой заметки живут в первых двух.

У них два ограничения: время жизни жёстко фиксировано — до конца программы или до конца блока, — а размер обычно ограничен несколькими мегабайтами. Третий вариант времени жизни даёт динамическая память: объект живёт от new до delete, независимо от блоков.

int* p = nullptr;
{
  int x = 11;
  p = new int(13);
}
std::cout << *p;  // Ok: объект из new пережил блок

Когда область действия объекта заканчивается раньше, чем область действия указателя на него, указатель становится висячим.

Углубление — L05

Локальную переменную можно объявить со словом static. Тогда у неё статическая область действия (static storage duration), как у глобальной, хотя имя видно только внутри функции. Из кода лекции, статические переменные:

  • инициализируются один раз;
  • сохраняют значение между вызовами;
  • по умолчанию инициализируются нулями.
int& G() {
  static int x;  // = 0
  return ++x;
}
 
G();  // 1
G();  // 2
G();  // 3

x не умирает в конце вызова, поэтому G может вернуть на неё ссылку — в отличие от обычной локальной переменной, см. Висячий указатель. static работает и во вложенном блоке: в функции H из лекции x и z создаются при каждом вызове, а static int y одна и растёт 1, 2, 3.

Не из лекции

Обнуление по умолчанию — свойство статической области действия. Обычная локальная int x; нулём не становится, см. Инициализация переменных.