Переменные в C: типы, объявление и области видимости

Переменные в C: типы, объявление и области видимости Полезное

Переменная в C — это именованная область памяти, в которой программа хранит значение определенного типа и может его читать, менять и использовать в вычислениях. Тип задается при объявлении и определяет, какие значения и в каком диапазоне ячейка может хранить.

Разберу переменные именно в языке C, а не в C++ или C#: базовые типы и их диапазоны, как объявлять и инициализировать, чем отличаются локальные, глобальные и static-переменные, зачем нужен const и какие ошибки чаще всего ловят новички. Весь код компилировал gcc; вывод показываю под каждым примером.

Первый рабочий пример

Чтобы не разбирать переменные в отрыве от кода, вот законченная программа: ее можно скопировать, собрать командой gcc main.c -o main и запустить. Она объявляет по одной переменной каждого базового типа, сразу инициализирует их и печатает.

#include <stdio.h>

int main(void) {
    int age = 25;
    float price = 19.99f;
    char grade = 'A';
    double pi = 3.14159265358979;

    printf("age = %d\n", age);
    printf("price = %.2f\n", price);
    printf("grade = %c\n", grade);
    printf("pi = %.5f\n", pi);
    return 0;
}

Вывод:

age = 25
price = 19.99
grade = A
pi = 3.14159

Разберу по строкам. int age = 25; — это объявление (называем тип и имя) и инициализация (сразу задаем значение) в одной строке. Дальше %d, %f, %c в printf — спецификаторы формата: они говорят, как вывести значение. Спецификатор должен соответствовать типу аргумента, иначе будет ошибка (пример ниже в разделе про частые ошибки).

Базовые типы данных и их диапазоны

Тип переменной определяет, что в ней можно хранить: целое число, дробное или символ. Вот основные типы C и типичные значения для 64-разрядной сборки gcc под Linux или macOS.

Тип Обычный размер Типичный диапазон Спецификатор printf
int 4 байта -2 147 483 648 .. 2 147 483 647 %d
unsigned int 4 байта 0 .. 4 294 967 295 %u
char 1 байт -128 .. 127 (если знаковый) %c или %d
float 4 байта около 6-7 значащих цифр %f
double 8 байт около 15-16 значащих цифр %f (в printf)
long 8 байт (Linux, macOS) от -9.2e18 до 9.2e18 %ld
_Bool (bool) 1 байт true или false %d

Важная граница: размеры типов в C не фиксированы стандартом, они зависят от платформы и ABI. Стандарт задает только минимумы через заголовки <limits.h> и <float.h>: int не меньше 16 бит, long не меньше 32, long long не меньше 64, char не меньше 8. На Windows (модель LLP64) long занимает 4 байта, а не 8. Поэтому в переносимом коде точные размеры берут из <stdint.h> (int32_t, int64_t), а границы конкретной сборки узнают из <limits.h>.

Тип char хранит символ, но по сути это маленькое целое: в переменной лежит код символа. Знаковый char или беззнаковый — решает компилятор (на x86 gcc он знаковый). Тип bool в C появился в стандарте C99; чтобы писать true и false, подключают <stdbool.h> (в C23 bool, true, false стали встроенными ключевыми словами).

Объявление и инициализация

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

int count;          // объявление без инициализации
count = 10;         // присваивание значения позже

int total = 0;      // объявление и инициализация сразу
double rate = 0.5;
char letter = 'x';  // символ - в одинарных кавычках

Имя переменной подчиняется правилам: начинается с буквы или подчеркивания (не с цифры), состоит из латинских букв, цифр и подчеркиваний, различает регистр (count и Count — разные переменные) и не совпадает с ключевым словом языка (int, return, for и т. п.). Пробелы и спецсимволы в имени недопустимы.

Совет: инициализируйте переменную сразу при объявлении. Локальная переменная без инициализации не обнуляется автоматически — в ней оказывается неопределенное значение, и чтение такого значения ведет к ошибке (об этом ниже).

Локальные, глобальные и static-переменные

Область видимости определяет, где в коде переменная доступна, а время жизни — как долго она существует. Три частых случая:

  • Локальная переменная объявлена внутри функции или блока { ... }. Видна только там и создается заново при каждом входе в блок.
  • Глобальная переменная объявлена вне всех функций. Видна во всем файле и существует все время работы программы.
  • static-переменная внутри функции локальна по видимости, но сохраняет значение между вызовами (живет все время работы программы).
#include <stdio.h>

int counter = 0;          // глобальная: видна всей программе

void tick(void) {
    static int calls = 0; // сохраняется между вызовами
    int local = 0;        // создается заново каждый вызов
    calls++;
    local++;
    counter++;
    printf("calls=%d local=%d counter=%d\n", calls, local, counter);
}

int main(void) {
    tick();
    tick();
    tick();
    return 0;
}

Вывод:

calls=1 local=1 counter=1
calls=2 local=1 counter=2
calls=3 local=1 counter=3

Видно разницу: local каждый раз рождается со значением 0 и после local++ равна 1. calls объявлена как static и помнит прошлое значение, поэтому растет: 1, 2, 3. Глобальная counter тоже накапливает изменения. Глобальными переменными не стоит злоупотреблять: их может изменить любая функция, и отследить, где значение испортилось, тяжело. Ключевое слово static у глобальной переменной означает другое — оно ограничивает ее видимость одним файлом.

Неизменяемые значения: const

Квалификатор const запрещает менять переменную после инициализации. Это удобно для констант вроде числа Пи или размера буфера: компилятор не даст случайно перезаписать значение.

#include <stdio.h>

int main(void) {
    const double PI = 3.14159;
    printf("PI = %.5f\n", PI);
    // PI = 3.2;   // ошибка компиляции, если раскомментировать
    return 0;
}

Если раскомментировать присваивание, gcc откажется собирать код с сообщением error: assignment of read-only variable 'PI'. Стоит различать: const в C — это обещание не менять переменную, а не всегда «настоящая» константа времени компиляции. Для задания размера массива на этапе компиляции в C традиционно используют макрос #define SIZE 100 или, в C23, ключевое слово constexpr.

Переменные в C, C++ и C# — в чем разница

Синтаксис объявления похож во всех трех языках, но детали отличаются. Путать их — частая ошибка новичка, поэтому развожу явно.

Признак C C++ C#
Тип bool нужен <stdbool.h> (с C99) встроен встроен
Локальная без инициализации неопределенное значение неопределенное значение (для встроенных типов) компилятор запрещает читать
Строки массив char[] или char * std::string тип string
Вывод типа нет (в C23 добавили auto) auto var
Где объявлять в C99+ в любом месте блока в любом месте в любом месте

Главное на старте: в C# нельзя прочитать локальную переменную, пока ей не присвоили значение, — это ловит компилятор. В C такой защиты нет, и ответственность за инициализацию лежит на разработчике.

Три частые ошибки

Ошибка 1: чтение неинициализированной переменной. Объявляю int x; и сразу печатаю, не задав значение:

#include <stdio.h>

int main(void) {
    int x;              // не инициализирована
    printf("x = %d\n", x);
    return 0;
}

Локальная переменная без инициализации получает неопределенное значение, и чтение его — неопределенное поведение. На экране может оказаться 0, мусор или разное число от запуска к запуску, полагаться на результат нельзя. gcc с флагом -Wall предупреждает об этом:

warning: 'x' is used uninitialized [-Wuninitialized]

Исправление — инициализировать при объявлении:

int x = 0;
printf("x = %d\n", x);   // x = 0

Ошибка 2: переполнение целого типа. Прибавляю единицу к максимальному int:

#include <stdio.h>
#include <limits.h>

int main(void) {
    int max = INT_MAX;
    printf("INT_MAX = %d\n", max);
    printf("max + 1 = %d\n", max + 1);
    return 0;
}

Типичный вывод на x86:

INT_MAX = 2147483647
max + 1 = -2147483648

Граница важна: по стандарту переполнение знакового типа — это неопределенное поведение. На практике gcc и clang с настройками по умолчанию дают заворачивание по правилам дополнительного кода (получается INT_MIN), но опираться на это нельзя — компилятор вправе оптимизировать код в расчете на то, что переполнения не бывает. У беззнаковых типов заворачивание по модулю определено стандартом. Исправление — взять тип пошире (long long) или проверять границу до сложения.

Ошибка 3: спецификатор формата не соответствует типу. Печатаю double через %d:

#include <stdio.h>

int main(void) {
    double price = 19.99;
    printf("%d\n", price);   // неверный спецификатор для double
    return 0;
}

%d ждет int, а получает double — это неопределенное поведение, на экране появится мусорное число или 0. gcc с -Wall предупреждает:

warning: format '%d' expects argument of type 'int', but argument 2 has type 'double' [-Wformat=]

Исправление — взять спецификатор под тип: printf("%.2f\n", price); напечатает 19.99.

Выводы

  • Переменная в C — это именованная типизированная область памяти; тип задается при объявлении и определяет допустимые значения и диапазон.
  • Размеры типов не фиксированы стандартом, а зависят от платформы; стандарт гарантирует лишь минимумы через <limits.h> и <float.h>, точные размеры дает <stdint.h>.
  • Локальная переменная видна только в своем блоке и не обнуляется сама; глобальная видна всему файлу; static-переменная внутри функции сохраняет значение между вызовами.
  • Инициализируйте переменные сразу: чтение неинициализированной локальной переменной — неопределенное поведение.
  • Переполнение знакового типа по стандарту не определено; на практике оно заворачивается по дополнительному коду, но закладываться на это нельзя.

Где применяется / связь с практикой

Переменные — это первое, с чего начинается любая программа на C: без них не сохранить ввод, не посчитать сумму, не завести счетчик цикла. Понимание типов и их диапазонов напрямую влияет на корректность: выбор int вместо long long для больших значений или float там, где нужна точность double, — типовой источник багов в реальном коде.

Освойте тему на практике

Разобрать типы, память, указатели и работу с переменными на практике можно на курсе cpp-basic в Otus. Посмотреть уровень и формат занятий, не оплачивая курс, помогут бесплатные вебинары — там разбирают конкретные задачи и отвечают на вопросы.

Смежные темы: return в C: что делает оператор и как работает, Как научиться программировать с нуля: с чего начать, Императивная разработка: характеристики и ее особенности.

FAQ

Чем объявление отличается от инициализации?
Объявление называет тип и имя переменной (int x;), инициализация задает начальное значение (int x = 5;). Можно объявить сейчас, а присвоить значение позже, но до присваивания читать переменную нельзя.

Что выбрать для дробных чисел — float или double?
По умолчанию берут double: у него около 15-16 значащих цифр против 6-7 у float, и литералы вроде 3.14 по умолчанию имеют тип double. float используют, когда важна экономия памяти на больших массивах и хватает меньшей точности.

Обязательно ли объявлять переменные в начале функции?
В старом стандарте C89 — да, все объявления шли в начале блока. Начиная с C99 переменную можно объявлять в любом месте, ближе к первому использованию; современные компиляторы это поддерживают.

OTUS Журнал
Скидка 5% 14-20 сентября на курсы (popup)