Тип данных в C — это правило, которое говорит компилятору, сколько памяти отвести под переменную и как трактовать ее биты: как целое со знаком, как код символа или как число с плавающей запятой. Одни и те же байты под разными типами читаются по-разному, поэтому тип определяет и диапазон значений, и поведение при переполнении.
Содержание
Ниже разберем встроенные арифметические типы языка C: целочисленные (char, short, int, long, long long), модификаторы знака (signed/unsigned), вещественные (float, double, long double), а также void и _Bool. Посмотрим, почему размеры зависят от платформы и что происходит при приведении и переполнении. Речь про типы языка C, а не про абстрактные структуры данных.
Целочисленные типы и их размеры
Базовые целочисленные типы упорядочены по неубыванию размера: char <= short <= int <= long <= long long. Стандарт задает не точные размеры, а минимальные гарантии — конкретные величины выбирает реализация (компилятор плюс платформа).
Ключевой факт: точные размеры платформозависимы. Ходовое «int — это 4 байта» верно на большинстве настольных систем, но стандарт гарантирует лишь минимум 16 бит. В таблице — минимумы стандарта и типичные значения на 64-битных Linux и macOS (модель LP64).
| Тип | Минимум по стандарту | Типично (LP64) | Типичный диапазон | printf |
|---|---|---|---|---|
char |
8 бит | 1 байт | знаковость платформозависима | %c |
signed char |
8 бит | 1 байт | -128..127 | %hhd |
unsigned char |
8 бит | 1 байт | 0..255 | %hhu |
short |
16 бит | 2 байта | -32768..32767 | %hd |
unsigned short |
16 бит | 2 байта | 0..65535 | %hu |
int |
16 бит | 4 байта | -2147483648..2147483647 | %d |
unsigned int |
16 бит | 4 байта | 0..4294967295 | %u |
long |
32 бита | 8 байт | +-9.2e18 | %ld |
unsigned long |
32 бита | 8 байт | 0..1.8e19 | %lu |
long long |
64 бита | 8 байт | +-9.2e18 | %lld |
unsigned long long |
64 бита | 8 байт | 0..1.8e19 | %llu |
Диапазоны в колонке «типично» приведены для дополнительного кода (two’s complement) — так знаковые целые представлены на массовых платформах. Минимальные гарантии стандарта чуть уже (для int это -32767..32767): исторически допускались и другие представления знака.
Строка long показательна: на 64-битных Linux и macOS это 8 байт, а на 64-битной Windows (модель LLP64) — 4 байта. Размер типа нельзя «запоминать числом» без привязки к платформе; когда нужен точно 64-битный тип везде, берут long long или int64_t из <stdint.h>.
Модификаторы signed и unsigned
Модификаторы задают, отведен ли старший бит под знак. signed хранит и отрицательные значения, unsigned — только неотрицательные, зато верхняя граница примерно вдвое больше.
Тонкость про char. В C это три РАЗНЫХ типа: char, signed char и unsigned char. Обычный char совпадает по значениям с одним из знаковых вариантов, но с каким — решает платформа (на x86 обычно знаковый, на части ARM — беззнаковый). Поэтому для байтовой арифметики пишут явно signed char или unsigned char, а char оставляют для текстовых символов. Слово int можно опускать: unsigned значит unsigned int, а long int и long — один тип.
sizeof: размер на своей платформе
Оператор sizeof возвращает размер типа или объекта в байтах. По определению sizeof(char) всегда равен 1, а размер байта в битах хранится в макросе CHAR_BIT (почти везде 8). Результат имеет тип size_t и печатается через %zu:
#include <stdio.h>
int main(void) {
printf("char %zu\n", sizeof(char));
printf("int %zu\n", sizeof(int));
printf("long %zu\n", sizeof(long));
printf("long long %zu\n", sizeof(long long));
printf("double %zu\n", sizeof(double));
return 0;
}
На типичной 64-битной Linux или macOS (LP64) вывод такой:
char 1
int 4
long 8
long long 8
double 8
На 64-битной Windows та же программа напечатает для long значение 4 — это не ошибка, а другая модель данных (LLP64). Правило: если размер важен, проверяйте sizeof или берите типы точной ширины.
Вещественные типы: float, double, long double
Вещественные типы хранят числа с плавающей запятой — с дробной частью и в широком диапазоне порядков. Их три: float (одинарная точность), double (двойная) и long double (расширенная).
На большинстве платформ float — это 32-битный формат IEEE 754 (около 7 значащих десятичных цифр), а double — 64-битный (около 15-16 цифр). Стандарт C, однако, не требует именно IEEE 754, а long double бывает разным: 80-битным на x86 или совпадающим с double. Это упрощенная модель — точные параметры берут из <float.h>.
Следствие двоичного представления: значения вроде 0.1 хранятся приближенно, поэтому сравнивать вещественные на строгое равенство (a == b) ненадежно — сравнивают по допуску. В printf и для float, и для double используют %f.
void и _Bool
void — это «отсутствие типа». Он служит тремя способами: функция без возврата (void f(void)), функция без параметров (int main(void)) и обобщенный указатель void *, который может указывать на данные любого типа.
_Bool появился в C99 и хранит логическое значение: любое присваивание, отличное от нуля, сохраняется как 1, ноль остается 0. Через <stdbool.h> доступны привычные имена bool, true, false. Например, bool flag = 5; сохранит в flag единицу.
Точные размеры и границы: stdint.h, limits.h, float.h
Когда программа должна вести себя одинаково на разных платформах (частый случай во встраиваемых системах и сетевых протоколах), берут типы точной ширины из <stdint.h>: int8_t, int16_t, int32_t, int64_t и беззнаковые uint8_t..uint64_t. Их размер фиксирован именем: int32_t — всегда ровно 32 бита.
Границы значений не заучивают, а читают из заголовков: целочисленные — из <limits.h>, вещественные — из <float.h>.
#include <stdio.h>
#include <limits.h>
int main(void) {
printf("CHAR_BIT = %d\n", CHAR_BIT);
printf("INT_MIN = %d\n", INT_MIN);
printf("INT_MAX = %d\n", INT_MAX);
printf("UINT_MAX = %u\n", UINT_MAX);
return 0;
}
Типичный вывод при 32-битном int:
CHAR_BIT = 8
INT_MIN = -2147483648
INT_MAX = 2147483647
UINT_MAX = 4294967295
Приведение типов и переполнение
Когда в выражении встречаются разные типы, C приводит их к общему по правилам обычных арифметических преобразований. Частый сюрприз новичка — целочисленное деление: если оба операнда целые, дробная часть отбрасывается до присваивания.
#include <stdio.h>
int main(void) {
int a = 7, b = 2;
printf("%d\n", a / b); // 3 - целочисленное деление
printf("%f\n", (double)a / b); // 3.500000 - приведение к double
return 0;
}
Вывод:
3
3.500000
Явное приведение (double)a делает один операнд вещественным, и деление становится вещественным. Это и есть «каст» — управляемая смена типа.
Переполнение зависит от знаковости. Для беззнаковых типов оно определено — результат берется по модулю 2^N (арифметика «по кругу»).
#include <stdio.h>
int main(void) {
unsigned char u = 255; // максимум для unsigned char
u = u + 1; // перенос по модулю 256
printf("%u\n", u); // 0
return 0;
}
Вывод:
0
А переполнение ЗНАКОВОГО целого (например, INT_MAX + 1) — это неопределенное поведение (undefined behavior): стандарт не гарантирует результата, и полагаться на «оно просто завернется в минус» нельзя — компилятор с оптимизацией может повести себя неожиданно. Поэтому перед сложением проверяют границы через INT_MAX/INT_MIN из <limits.h> либо осознанно берут беззнаковый тип там, где перенос по модулю — нужное поведение.
Выводы
- Тип в C задает и размер памяти, и трактовку битов; стандарт фиксирует минимальные гарантии, а точные размеры выбирает платформа.
- «int равен 4 байтам» — лишь типичный случай; гарантирован минимум 16 бит, а
longна Windows и Linux отличается. Проверяйтеsizeofили беритеint32_t,int64_tиз<stdint.h>. signed/unsignedуправляют знаком; уcharзнаковость платформозависима, поэтому для байтовой арифметики пишутsigned charилиunsigned char.- Границы и параметры точности не заучивают, а читают из
<limits.h>и<float.h>. - Целочисленное деление отбрасывает дробь; беззнаковое переполнение определено (по модулю 2^N), знаковое — неопределенное поведение.
Где применяется / связь с практикой
Понимание системы типов C — база для низкоуровневого программирования: работы с памятью, битовыми операциями, сетевыми протоколами и драйверами, где размер каждого поля важен побайтно. Ошибка в выборе типа (знаковый вместо беззнакового, узкий тип для большого счетчика) — классический источник багов, которые всплывают только на больших данных.
Освойте тему на практике
Разобрать эти механики с компилятором и отладчиком удобнее на структурированном курсе. На курсе «Программист C» типы, память и приведения отрабатываются на реальных задачах. Посмотреть формат и уровень занятий можно на открытых уроках Otus — это бесплатно и помогает выбрать направление.
FAQ
Почему sizeof(int) на моей машине равен 4, а где-то пишут 2?
Стандарт гарантирует для int только минимум 16 бит (2 байта). На старых и микроконтроллерных платформах int бывает 2 байта, на современных настольных — обычно 4. Точное значение всегда покажет sizeof на вашей системе.
Чем int32_t лучше обычного int?
int32_t гарантированно ровно 32 бита там, где он определен, а int может быть 16 или 32 бита. Когда важен точный размер поля (формат файла, сетевой пакет, регистр устройства), берут типы из <stdint.h>; для обычных счетчиков достаточно int.
Что будет, если сложить два больших int и выйти за диапазон?
Для знакового int это неопределенное поведение — результат не гарантирован. Для беззнаковых типов сложение определено и идет по модулю 2^N. Если перенос нежелателен, проверяйте границы через INT_MAX или берите более широкий тип.



