Переменная в 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 переменную можно объявлять в любом месте, ближе к первому использованию; современные компиляторы это поддерживают.



