В зависимости от архитектуры размер базовых типов, таких как int
или long
может меняться.
Например, для типа int стандартом языка Си устанавливается минимальный размер в 2 байта (16 бит), а для типа long - минимальный размер в 4 байта (32 бита).
На большинстве современных архитектур int занимает 4 байта, но тем не менее это не обязательное требование. Или к примеру, возьмем следующую простейшую программу:
#include <stdio.h> int main(void) { printf("sizeof(long) = %zu \n", sizeof(long)); return 0; }
Даже если мы возьмем только 64-разрядные системы, то окажется, что на Windows long занимает 4 байта, тогда как на Linux - 8 байт.
Для описания применяемых размеров в языке Си используются специальный набор характеристик, который называет модель данных или data model. Существует множество моделей данных, поэтому отмечу только распространенные:
LP32: модель 2/4/4 (int - 2 байта, long и указатели по 4 байта). Применяется для 32-разрядных систем в Win16 API
ILP32: модель 4/4/4 (int, long и указатели по 4 байта). Применяется для 32-разрядных систем в Win32 API, Unix и Unix-подобных системах (Linux, macOS)
LLP6: модель 4/4/8 (int и long по 4 байта, указатель - 8 байт). Применяется для 64-разрядных систем ARM (AArch64) и x86-64 (x64) в Win32 API (также называемом Windows API) или грубо говоря в современном Windows
LP64: модель 4/8/8 (int - 4 байта, long и указатели по 8 байт) Применяется для 64-разрядных Unix и Unix-подобных системах (Linux, macOS) (грубо говоря, в современных Linux и MacOS)
Размеры в битах для разных типов в соответствии с выше перечисленными моделями:
Тип данных | Стандарт C | LP32 | ILP32 | LLP64 | LP64 |
char/unsigned char | Как минимум 8 | 8 | 8 | 8 | 8 |
short/unsigned short int | Как минимум 16 | 16 | 16 | 16 | 16 |
int / unsigned int | Как минимум 16 | 16 | 32 | 32 | 32 |
long/unsigned long | Как минимум 32 | 32 | 32 | 32 | 64 |
long long/unsigned long long | Как минимум 64 | 64 | 64 | 64 | 64 |
Чтобы уйти от платформенной зависимости начиная со стандарта C99 в язык была добавлена поддержка платформо-независимых числовых типов. Они определены в заголовчном файле
stdint.h и имеют названия по следующему шаблону uint|int[размер]_t
. В качестве размера указывается количество бит - 8, 16, 32 или 64:
int8_t
int16_t
int32_t
int64_t
intptr_t (для хранения указателей)
Беззнаковые типы предваряются приставкой u
uint8_t
uint16_t
uint32_t
uint64_t
uintptr_t (для хранения указателей)
Пример применения:
#include <stdio.h> #include <stdint.h> int main(void) { int8_t i8 = -8; uint8_t u8 = 8; int16_t i16 = -16; uint16_t u16 = 16; int32_t i32 = -32; uint32_t u32 = 32; int64_t i64 = -64; uint64_t u64 = 64; printf("i8 = %d \n", i8); printf("u8 = %u \n", u8); printf("i16 = %d \n", i16); printf("u16 = %u \n", u16); printf("i32 = %d \n", i32); printf("u32 = %u \n", u32); printf("i64 = %lld \n", i64); printf("u64 = %llu \n", u64); return 0; }
В примере выше для вывода на консоль применяются стандартные встроенные спецификаторы типа %d
или %lld
.
Однако специально для этих типов в заголовочном файле inttypes.h также определены дополнительные макросы:
PRI для форматированного вывода (с помощью printf, fprintf и т.д.)
SCN для форматированного ввода (scanf, fscanf и т.д.).
Вместе с этими макросами применяются специафикаторы типов:
d: для вывода числе в десятичном формате
x: в шестнадцатеричном формате
o: в восьмеричном формате
u: для беззнаковых чисел
i: для целых чисел со знаком
Применение
#include <stdio.h> #include <inttypes.h> int main(void) { int64_t int64; uint64_t uint64; printf("Enter signed number: "); scanf("%"SCNi64, &int64); printf("Enter unsigned number: "); scanf("%"SCNu64, &uint64); printf("int64 = %" PRIi64 "\n", int64); printf("uint64 = %" PRIu64 "\n", uint64); return 0; }
Пример ввода:
Enter signed number: -34 Enter unsigned number: 56 int64 = -34 uint64 = 56
Стандартные операции, например, арифметика, производятся также, как и со встроенными типами:
#include <stdio.h> #include <inttypes.h> int main(void) { int64_t a = 5; int64_t b = 6; int64_t c = a + b * a; // c = 35 printf("c = %" PRIi64 "\n", c); return 0; }