Область видимости и время жизни переменных
Урок различает два разных понятия — область видимости (где имя доступно) и время жизни (когда переменная существует в памяти), и разбирает локальные, глобальные и 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 внутри функции совмещает локальную видимость с временем жизни всей программы. Главное правило — объявлять переменные в самой узкой области и избегать глобальных.