Menu

Типы данных в C: int, float, char, double и другие

У каждой переменной в C есть тип, который задаёт её размер и то, что она может хранить. Здесь — все базовые типы, их реальные размеры, знаковые и беззнаковые варианты, sizeof, limits.h и что происходит при переполнении.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

У каждой переменной в C есть тип, который вы выбираете при объявлении и который остаётся с ней навсегда. Тип решает три вещи: сколько байт занимает переменная, как эти байты интерпретируются и какие операции над ней осмысленны.

Список типов в C короткий. Вся сложность — в модификаторах.

Четыре базовых типа

  • char занимает один байт. Используется для отдельных символов ('A'), для байтов строки и иногда как крошечное целое.
  • int — рабочая лошадка среди целочисленных типов. Счётчики циклов, размеры, идентификаторы.
  • float и double хранят вещественные числа. У double примерно вдвое больше точность.
  • void в некотором смысле четвёртый тип, но он означает «нет значения» — переменную типа void объявить нельзя, его используют только как тип возврата функции, как пустой список параметров или как нетипизированный указатель.

Обратите внимание на суффикс f в 3.14f. Без него литерал имеет тип double, и присваивание его переменной float молча теряет точность. Некоторые компиляторы об этом предупреждают.

Модификаторы: short, long, unsigned

У базовых целочисленных типов есть модификаторы, меняющие размер или знак.

Каждому целочисленному типу нужен свой спецификатор формата: %hd для short, %d для int, %ld для long, %lld для long long, %u для unsigned. Использовать не тот — это неопределённое поведение, а не ошибка округления.

unsigned убирает знаковый бит и удваивает положительный диапазон. unsigned int хранит значения от 0 до примерно 4,3 млрд вместо диапазона от -2,1 до +2,1 млрд. Это правильный выбор для того, что действительно не может быть отрицательным — размеров, количества байт, битовых масок, — и ловушка для всего, что может быть уменьшено ниже нуля.

signed подразумевается по умолчанию для int, short, long и long long, поэтому его почти никогда не пишут. Единственное место, где это важно, — char: знаковый ли простой char или беззнаковый, определяется реализацией, поэтому пишите signed char или unsigned char, когда знак имеет значение.

Размеры: что вы получаете на самом деле

Стандарт C задаёт минимумы, а не точные размеры. На любой современной 64-битной машине с Linux, macOS или Windows вы увидите вот это:

ТипТипичный размерТипичный диапазон
char1 байтот -128 до 127 (или от 0 до 255)
short2 байтаот -32 768 до 32 767
int4 байтаот -2 147 483 648 до 2 147 483 647
long8 байт (4 в Windows)примерно ±9,2 квинтиллиона
long long8 байтпримерно ±9,2 квинтиллиона
float4 байта~7 значащих цифр
double8 байт~15 значащих цифр
long double16 байт (зависит от платформы)больше, чем у double

Строка про long — та самая, на которой попадаются: это 8 байт в Linux и macOS и 4 байта в 64-битной Windows. Код, который считает, что long вмещает 64-битное значение, непереносим. Используйте long long или типы точной ширины из stdint.h (int32_t, uint64_t), когда размер — часть требований.

sizeof: спросите у компилятора

Никогда не угадывайте размер — измеряйте его:

sizeof — это оператор, а не функция, и вычисляется он на этапе компиляции. Он даёт значение типа size_t, которое печатается через %zu.

sizeof(char) гарантированно равен ровно 1 — это и есть определение байта в C. Всё остальное измеряется относительно него.

limits.h и float.h

Точные диапазоны для вашего компилятора доступны в виде именованных констант:

Именно с этими значениями нужно сравнивать, когда вы хотите понять, не переполнится ли операция. Проверка if (a > INT_MAX - b) перед вычислением a + b — способ обнаружить переполнение до того, как оно случится, а это важно, потому что знаковое переполнение нельзя обнаружить постфактум.

Переполнение целых чисел

Что произойдёт, когда значение выйдет за пределы диапазона своего типа, целиком зависит от знака.

Беззнаковое переполнение определено: значение сворачивается по модулю 2^N.

Второй случай — настоящий источник багов. Цикл вида for (unsigned i = n - 1; i >= 0; i--) никогда не закончится, потому что беззнаковое значение всегда >= 0.

Знаковое переполнение — неопределённое поведение. Не «сворачивается», а именно неопределённое. Компилятору разрешено считать, что оно никогда не случается, и оптимизировать исходя из этого, а значит, проверку на переполнение, написанную после факта, он может удалить:

int sum = a + b;
if (sum < a) { /* компилятор вправе полностью убрать эту проверку */ }

Проверяйте заранее, используя пределы:

if (b > 0 && a > INT_MAX - b) {
    /* a + b переполнится - обработайте это */
}

Точность чисел с плавающей точкой

float и double хранят числа в двоичном виде, а у большинства десятичных дробей нет точной двоичной записи — ровно так же, как у 1/3 нет точной десятичной.

Отсюда правило: никогда не сравнивайте числа с плавающей точкой через ==. Сравнивайте абсолютную разницу с небольшим допуском.

И никогда не используйте плавающую точку для денег. Храните копейки как целое: long long копеек точен там, где double рублей — нет.

Как выбрать тип

Короткий список решений, покрывающий большую часть кода:

  • Целые числа: int, если нет причины взять другое. Это тип, который процессор обрабатывает эффективнее всего и вокруг которого построены все правила арифметики.
  • Что-то больше 2 миллиардов: long long или int64_t из stdint.h.
  • Размеры, длины, индексы массивов из sizeof или strlen: size_t. Он беззнаковый и гарантированно вмещает размер любого объекта.
  • Дробные числа: double. Берите float только чтобы вдвое сократить память в больших массивах или на встраиваемом железе без блока двойной точности.
  • Отдельные символы и сырые байты: char для текста, unsigned char для двоичных данных.
  • Истина/ложь: bool из stdbool.h — см. логический тип в C.
  • Точная разрядность (форматы файлов, сетевые протоколы, регистры устройств): stdint.huint8_t, int16_t, uint32_t и так далее.

Смешивание типов

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

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

Эти правила преобразования и то, как взять их под контроль с помощью явных приведений, — тема страницы о приведении типов в C.

Часто задаваемые вопросы

Какие базовые типы данных есть в C?

Четыре базовых типа: char для отдельных символов и байтов, int для целых чисел, float и double для дробных. Модификаторы меняют их размер и знак — short, long, long long, signed и unsigned — и дают полный набор.

Сколько байт занимает int в C?

Почти всегда 4 байта (32 бита) на современных настольных и серверных системах, что даёт диапазон примерно от -2,1 млрд до 2,1 млрд. Стандарт гарантирует лишь 2 байта минимум, и 16-битные микроконтроллеры действительно используют 2. Если нужно знать точно, используйте sizeof(int).

В чём разница между float и double в C?

float — 4 байта и около 7 значащих десятичных цифр; double — 8 байт и около 15. double используется по умолчанию для литералов с плавающей точкой и математических функций, и если вы не храните миллионы значений и не пишете под встраиваемый чип, правильный выбор — double.

Что происходит при переполнении int в C?

Для знакового int переполнение — это неопределённое поведение: компилятор может свернуть значение, насытить его или вовсе выбросить вашу проверку. Для беззнакового int всё определено строго: значение сворачивается по модулю 2^N, поэтому UINT_MAX + 1 равно 0. Никогда не полагайтесь на знаковое переполнение.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ