Область видимости и время жизни переменных

Урок различает два разных понятия — область видимости (где имя доступно) и время жизни (когда переменная существует в памяти), и разбирает локальные, глобальные и static-переменные.
Область видимости и время жизни — это не одно и то же. Локальная переменная видна только в своём блоке и живёт только во время его выполнения. Static-переменная видна локально, но живёт всю программу.

Локальная переменная объявлена внутри функции или блока. Она видна только там и существует, только пока выполняется этот блок. При каждом вызове функции локальные переменные создаются заново:

void counter(void) {
    int c = 0;     // создаётся заново при каждом вызове
    c++;
    printf("%d\n", c);   // всегда печатает 1
}

Глобальная переменная объявлена вне всех функций. Она видна во всём файле и живёт всю программу. Но злоупотребление глобальными переменными делает код запутанным и опасным.

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

void counter(void) {
    static int c = 0;   // создаётся ОДИН раз, живёт всю программу
    c++;
    printf("%d\n", c);   // печатает 1, 2, 3 ...
}

Как работает под капотом

Где физически живут переменные? Память программы делится на области. Локальные переменные ложатся на стек и исчезают при выходе из функции; глобальные и static — в отдельный сегмент, который существует всё время работы программы.

Память программы:
  +---------------------------+  высокие адреса
  |   Стек (stack)            |  <- локальные переменные
  |   растёт вниз  v          |     живут только в своём блоке
  |        ...                |
  |        ^ растёт вверх     |
  |   Куча (heap)             |  <- malloc (позже)
  +---------------------------+
  |   Глобальные / static     |  <- живут всю программу
  +---------------------------+
  |   Код программы           |
  +---------------------------+  низкие адреса

Вот почему обычная локальная c всегда печатает 1: при каждом вызове она заново создаётся на стеке со значением 0. А static int c лежит не на стеке, а в сегменте статических данных, поэтому сохраняет накопленное значение.

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

  • Возврат адреса локальной переменной. После выхода из функции память на стеке освобождается, адрес становится недействительным.
  • Чрезмерное использование глобальных переменных. Любая функция может их изменить, и отследить, кто и когда это сделал, очень тяжело.
  • Затенение имён. Локальная переменная с тем же именем, что и глобальная, «перекрывает» её внутри блока — легко запутаться.
  • Ожидание, что локальная переменная сохранит значение между вызовами. Без static она обнуляется/пересоздаётся.

Best practices

  • Объявляйте переменные в максимально узкой области — прямо там, где они нужны. Это снижает риск ошибок.
  • Избегайте глобальных переменных. Если данные нужны нескольким функциям — передавайте их через параметры.
  • Используйте static осознанно: для счётчиков, кэшей и состояний, которые должны пережить отдельный вызов.

Разницу между «пересоздаётся каждый раз» и «живёт между вызовами» удобно показать на Python через локальную переменную и атрибут функции.

# Локальная переменная — пересоздаётся (как обычная в C)
def local_counter():
    c = 0
    c += 1
    return c

print("Локальный счётчик:", local_counter(), local_counter(), local_counter())

# Эмуляция static — состояние сохраняется между вызовами
def static_counter():
    static_counter.c = getattr(static_counter, "c", 0) + 1
    return static_counter.c

print("Static-счётчик:   ", static_counter(), static_counter(), static_counter())

Та же логика на Python ▶ — локальный счётчик всегда выдаёт 1, а «static» накапливает 1, 2, 3. Точно так же ведут себя обычная и static-переменная в C.

Ключевое слово const

Помимо управления временем жизни, в C есть инструмент управления изменяемостью — const. Объявив const int MAX = 100;, вы запрещаете менять переменную: компилятор отвергнет любую попытку присваивания. Особенно ценен const в параметрах функций: void print(const char *s) сообщает и компилятору, и читателю кода, что функция только читает строку, но не портит её. Это не просто страховка от опечаток — это документация, встроенная в тип, и она помогает компилятору оптимизировать код. Хорошая привычка — помечать const всё, что не должно меняться: указатели на входные данные, константы, табличные значения. Чем больше инвариантов выражено в типах, тем меньше остаётся места для ошибок.

Итоги

Область видимости — где имя доступно; время жизни — когда переменная существует. Локальные переменные видны в своём блоке и живут на стеке только во время его выполнения. Глобальные видны везде и живут всю программу, но опасны. static внутри функции совмещает локальную видимость с временем жизни всей программы. Главное правило — объявлять переменные в самой узкой области и избегать глобальных.

Проверьте себя
1. Чем static int c внутри функции отличается от обычной int c?
Astatic-переменная видна во всём файле
Bstatic-переменная создаётся один раз и сохраняет значение между вызовами функции
Cstatic-переменная работает быстрее
DНикакой разницы нет
2. Где в памяти живут локальные переменные функции?
AВ куче (heap)
BВ сегменте кода
CНа стеке (stack) — и исчезают при выходе из функции
DВ сегменте глобальных данных