Переменные и базовые типы данных
Урок разбирает переменные и базовые типы C: int, char, float, double, как они хранятся в памяти и почему в C размер типа — это не абстракция, а конкретные байты.
В C переменная — это именованный участок памяти фиксированного размера. Тип переменной определяет и сколько байтов она занимает, и как эти байты интерпретируются.
В отличие от Python, где переменная — это просто метка, способная указывать на что угодно, в C переменная жёстко привязана к типу. Когда вы пишете int x;, компилятор резервирует под x ровно столько байтов, сколько занимает целое число на вашей платформе (обычно 4 байта), и навсегда запоминает: здесь лежит целое. Поменять тип нельзя.
#include <stdio.h>
int main(void) {
int age = 25; // целое число
float price = 19.99f; // вещественное (одинарная точность)
double pi = 3.14159265; // вещественное (двойная точность)
char grade = 'A'; // один символ (хранится как число!)
printf("Возраст: %d\n", age);
printf("Цена: %.2f\n", price);
printf("Оценка: %c (код %d)\n", grade, grade);
return 0;
}
Обратите внимание: char хранит символ, но физически это маленькое целое число — код символа в таблице ASCII. Буква 'A' — это число 65. Поэтому grade можно вывести и как символ (%c), и как число (%d).
Как работает под капотом
Каждый тип занимает определённое число байтов. Посмотрим, как переменные раскладываются в памяти (размеры типичны для 64-битной системы):
Имя Тип Размер Память (байты) ------- ------- ------- ------------------------ grade char 1 байт [65] age int 4 байта [00][00][00][25] price float 4 байта [.. .. .. ..] pi double 8 байт [.. .. .. .. .. .. .. ..]
Размер типа можно узнать оператором sizeof. Именно размер задаёт диапазон значений: int в 4 байта (32 бита) вмещает примерно от минус двух до плюс двух миллиардов. Если число выйдет за этот предел, произойдёт переполнение — значение «завернётся» и станет некорректным. Это одна из коварных особенностей C.
printf("int занимает %zu байт\n", sizeof(int));
printf("char занимает %zu байт\n", sizeof(char));
printf("double занимает %zu байт\n", sizeof(double));
Частые ошибки
- Использование неинициализированной переменной.
int x;без присваивания содержит мусор — то, что случайно лежало в этой памяти. Чтение даст непредсказуемый результат. - Путаница
=и==. Первое — присваивание, второе — сравнение.if (x = 5)вместоif (x == 5)— классическая ошибка. - Переполнение целого. Прибавление к максимальному
intединицы даёт большое отрицательное число, а не ошибку. - Сравнение
floatна точное равенство. Из-за двоичного представления0.1 + 0.2не равно ровно0.3.
Best practices
- Инициализируйте переменные при объявлении:
int count = 0;. Это убирает целый класс багов. - Выбирайте тип под смысл: для денег и точных расчётов —
double, для счётчиков —int, для размеров и индексов —size_t. - Для целых фиксированного размера в современном C используйте
<stdint.h>:int32_t,uint8_t— их размер одинаков на всех платформах.
Идею «у каждого типа свой размер и диапазон» удобно прочувствовать на Python, где мы вручную смоделируем переполнение 8-битного беззнакового числа.
# Эмулируем uint8_t: диапазон 0..255, переполнение "заворачивается"
def add_uint8(a, b):
return (a + b) % 256
print("250 + 10 в uint8_t =", add_uint8(250, 10)) # не 260, а 4!
print("255 + 1 в uint8_t =", add_uint8(255, 1)) # заворот в 0
Та же логика на Python ▶ — в реальном C переполнение происходит само, без всякого % 256, и часто незаметно. Понимание границ типа спасает от таких сюрпризов.
Знаковость и беззнаковость
У целочисленных типов есть важное измерение, которое легко упустить: знаковость. int хранит и отрицательные, и положительные числа, а unsigned int — только неотрицательные, зато вдвое больший положительный диапазон. Смешивать их в одном выражении опасно: при сравнении signed и unsigned компилятор молча приводит знаковое к беззнаковому, и отрицательное число превращается в огромное положительное. Классическая ловушка — цикл for (unsigned i = n; i >= 0; i--): он никогда не закончится, потому что unsigned не бывает меньше нуля. Поэтому для счётчиков, которые могут стать отрицательными, берут знаковый тип, а для размеров и индексов — size_t (он беззнаковый и точно вмещает любой размер объекта в памяти).
Итоги
Переменная в C — это типизированный участок памяти фиксированного размера. Базовые типы: int (целые), float и double (вещественные), char (символ-число). Размер типа определяет диапазон значений и узнаётся через sizeof. Главные опасности — неинициализированные переменные и переполнение; защита от них — инициализация при объявлении и продуманный выбор типа.