Система типов Си
Система типов Си — реализация понятия типа данных в языке программирования Си. Сам язык предоставляет базовые арифметические типы, а также синтаксис для создания массивов и составных типов. Некоторые заголовочные файлы из стандартной библиотеки Си содержат определения типов с дополнительными свойствами[1][2].
Базовые типы[править | править код]
Язык Си предоставляет множество базовых типов. Большинство из них формируется с помощью одного из четырёх арифметических спецификаторов типа, (char
, int
, float
и double
), и опциональных спецификаторов (signed
, unsigned
, short
и long
). Хотя стандартом установлен диапазон, вычисляемый по формуле от −(2n−1−1) до 2n−1−1, все известные компиляторы (gcc, clang и Microsoft compiler) допускают диапазон от −(2n−1) до 2n−1−1, где n — разрядность типа.
В таблице ниже предполагается, что 1 байт = 8 битам.
На подавляющем большинстве современных платформ это так, однако возможна ситуация, когда 1 байт равняется 16 битам или какому-то другому числу, как правило степени двойки.
Тип | Пояснение | Спецификатор формата |
---|---|---|
char |
Целочисленный, самый маленький из возможных адресуемых типов. Может содержать базовый набор символов. Может быть как знаковым, так и беззнаковым, зависит от реализации. Содержит CHAR_BIT (как правило, 8) бит.[3] |
%c
|
signed char |
Того же размера что и char , но гарантированно будет со знаком. Может принимать значения как минимум из диапазона [−127, +127] [3], обычно в реализациях [−128, +127] [4] |
%c (также %d или %hhi (%hhx , %hho ) для вывода в числовой форме)
|
unsigned char |
Того же размера что и char , но гарантированно без знака. Диапазон: [0, 2CHAR_BIT − 1] [3]. Как правило, [0, 255] |
%c (или %hhu для вывода в числовой форме)
|
short short int signed short signed short int |
Тип короткого целого числа со знаком. Может содержать числа как минимум из диапазона [−32767, +32767] [3], обычно в реализациях [−32768, +32767] [4]. Таким образом, это по крайней мере 16 бит (2 байта). |
%hi
|
unsigned short unsigned short int |
Такой же, как short , но беззнаковый. Диапазон: [0, +65535] |
%hu
|
int signed signed int |
Основной тип целого числа со знаком. Может содержать числа как минимум из диапазона [−32767, +32767] [3]. Таким образом, это по крайней мере 16 бит (2 байта). Как правило, в современных компиляторах для 32- и более -разрядных платформ имеет размер 4 байта и диапазон [−2 147 483 648, +2 147 483 647] , однако на 16- и 8-битных платформах имеет размер, как правило, 2 байта в диапазоне значений [−32768, +32767] , что часто вызывает путаницу и приводит к несовместимости неаккуратно написанного кода |
%i или %d
|
unsigned unsigned int |
Такой же как int , но беззнаковый. Диапазон: [0, +4 294 967 295] |
%u
|
long long int signed long signed long int |
Тип длинного целого числа со знаком. Может содержать числа, как минимум, в диапазоне [−2 147 483 647, +2 147 483 647] .[3][4][5]Таким образом, это по крайней мере 32 бита (4 байта). |
%li или %ld
|
unsigned long unsigned long int |
Такой же как long , но беззнаковый. Диапазон: [0, +4 294 967 295] |
%lu
|
long long long long int signed long long signed long long int |
Тип длинного длинного (двойного длинного) целого числа со знаком. Может содержать числа как минимум в диапазоне [−9 223 372 036 854 775 808, +9 223 372 036 854 775 807] .[3][4] Таким образом, это по крайней мере 64 бита. Утверждён в стандарте C99. |
%lli или %lld
|
unsigned long long unsigned long long int |
Похож на long long , но беззнаковый. Диапазон : [0, 18 446 744 073 709 551 615] . |
%llu
|
float |
Тип вещественного числа с плавающей запятой, обычно называемый типом числа одинарной точности с плавающей запятой. Подробные свойства в стандарте не указаны (за исключением минимальных пределов), однако на большинстве систем это IEEE 754 бинарный формат с плавающей запятой одинарной точности. Этот формат требуется для опциональной арифметики с плавающей запятой Annex F «IEC 60559 floating-point arithmetic». | %f (автоматически преобразуется в double для printf() )
|
double |
Тип вещественного числа с плавающей запятой, обычно называемый типом числа двойной точности с плавающей запятой. На большинстве систем соответствует IEEE 754 бинарный формат с плавающей запятой двойной точности. | %f (%F )
( |
long double |
Тип вещественного числа с плавающей запятой, обычно ставящийся в соответствие к формату числа повышенной точности с плавающей запятой. В отличие от float и double , может быть 80-битным форматом с плавающей запятой, не-IEEE «double-double» или «IEEE 754 бинарный формат с плавающей запятой четырёхкратной точности». Если более точного формата не предоставлено, эквивалентен double . Смотрите the article on long double для подробностей. |
%Lf %LF %Lg %LG %Le %LE [6]
|
Также не были упомянуты следующие спецификаторы типов: (%s
для строк, %p
для указателей, %x
(%X
) для шестнадцатеричного представления, %o
для восьмеричного.
Реальный размер целочисленных типов зависит от реализации. Стандарт лишь оговаривает отношения в размерах между типами и минимальные рамки для каждого типа:
Так long long
не должен быть меньше long
, который в свою очередь не должен быть меньше int
, который в свою очередь не должен быть меньше short
. Так как char
— наименьший из возможных адресуемых типов, другие типы не могут иметь размер меньше него.
Минимальный размер для char
— 8 бит, для short
и int
— 16 бит, для long
— 32 бита, для long long
— 64 бита.
Желательно, чтобы тип int
был таким целочисленным типом, с которым наиболее эффективно работает процессор. Это позволяет достигать высокой гибкости, например, все типы могут занимать 64 бита. Однако, есть популярные схемы, описывающие размеры целочисленных типов.[7]
На практике это означает, что char
занимает 8 бит, а short
16 бит (также, как и их беззнаковые аналоги). int
на большинстве современных платформ занимает 32 бита, а long long
64 бита. Длина long
варьируется: для Windows это 32 бита, для UNIX-подобных систем — 64 бита.
Стандарт C99 включает новые вещественные типы: float_t
и double_t
, определённые в <math.h>
. Также он включает комплексные типы: float _Complex
, double _Complex
, long double _Complex
.
Логический тип[править | править код]
В C99 был добавлен логический тип _Bool
. Также, дополнительный заголовочный файл <stdbool.h>
определяет для него псевдоним bool
, а также макросы true
(истина) и false
(ложь). _Bool
ведёт себя так же, как обычный встроенный тип, за одним исключением: любое ненулевое (не ложное) присваивание _Bool
хранится как единица. Такое поведение защищает от переполнения. Например:
unsigned char b = 256;
if (b) {
/* do something */
}
b
считается ложным, если unsigned char
занимает 8 бит. Однако, смена типа делает переменную истинной:
_Bool b = 256;
if (b) {
/* do something */
}
Типы размера и отступа указателя[править | править код]
Спецификация языка C включает обозначения типов (typedef) size_t
и ptrdiff_t
. Их размер определяется относительно арифметических возможностей процессора. Оба этих типа определены в <stddef.h>
(cstddef
для C++).
size_t
— беззнаковый целый тип, предназначенный для представления размера любого объекта в памяти (включая массивы) в конкретной реализации. Оператор sizeof
возвращает значение типа size_t
. Максимальный размер size_t
записан в макроконстанте SIZE_MAX
, определённой в <stdint.h>
(cstdint
для C++). size_t
должен быть, как минимум, 16 бит. К тому же POSIX включает ssize_t
, который является встроенным знаковым типом, по размеру равным size_t
.
ptrdiff_t
— это встроенный знаковый тип, который определяет разность между указателями. Гарантируется, что он будет действовать с указателями одного и того же типа. Арифметика между указателями разных типов зависит от реализации.
Интерфейс к свойствам базовых типов[править | править код]
Информация о фактических свойствах, таких как размер, основных встроенных типов предоставлена через макро-константы в двух заголовках: заголовок <limits.h>
(climits
в C++) определяет макросы для целочисленных типов, заголовок <float.h>
(cfloat
в C++) определяет макросы для вещественных типов. Конкретные значения зависят от реализации.
- Свойства целочисленных типов
CHAR_BIT
— размерchar
в битах (минимум 8 бит)SCHAR_MIN
,SHRT_MIN
,INT_MIN
,LONG_MIN
,LLONG_MIN
(C99) — минимальные возможные значения знаковых целых типов:signed char
,signed short
,signed int
,signed long
,signed long long
SCHAR_MAX
,SHRT_MAX
,INT_MAX
,LONG_MAX
,LLONG_MAX
(C99) — максимальные возможные значения знаковых целых типов:signed char
,signed short
,signed int
,signed long
,signed long long
UCHAR_MAX
,USHRT_MAX
,UINT_MAX
,ULONG_MAX
,ULLONG_MAX
(C99) — максимальные возможные значения беззнаковых целых типов:unsigned char
,unsigned short
,unsigned int
,unsigned long
,unsigned long long
CHAR_MIN
— минимальное возможное значениеchar
CHAR_MAX
— максимальное возможное значениеchar
MB_LEN_MAX
— максимальное число байт в многобайтовых символьных типах.
- Свойства вещественных типов
FLT_MIN
,DBL_MIN
,LDBL_MIN
— минимальное нормализованное положительное значение дляfloat
,double
,long double
соответственноFLT_TRUE_MIN
,DBL_TRUE_MIN
,LDBL_TRUE_MIN
(C11) — минимальное положительное значение дляfloat
,double
,long double
соответственноFLT_MAX
,DBL_MAX
,LDBL_MAX
— максимальное конечное значение дляfloat
,double
,long double
соответственноFLT_ROUNDS
— способ округления для целочисленных операцийFLT_EVAL_METHOD
(C99) — метод оценки выражений, включающих различные типы с плавающей запятойFLT_RADIX
— основание экспоненты вещественных типов.FLT_DIG
,DBL_DIG
,LDBL_DIG
— число десятичных цифр, которые могут быть представлены, не теряя точность дляfloat
,double
,long double
соответственноFLT_EPSILON
,DBL_EPSILON
,LDBL_EPSILON
— разница между 1.0 и следующим числом дляfloat
,double
,long double
соответственноFLT_MANT_DIG
,DBL_MANT_DIG
,LDBL_MANT_DIG
— количество цифр в мантиссе дляfloat
,double
,long double
соответственноFLT_MIN_EXP
,DBL_MIN_EXP
,LDBL_MIN_EXP
— минимальное целое отрицательное число, такое чтоFLT_RADIX
, возведённое в степень на единицу меньше нормализованногоfloat
,double
,long double
соответственноFLT_MIN_10_EXP
,DBL_MIN_10_EXP
,LDBL_MIN_10_EXP
— минимальное отрицательное целое число такое, что 10, возведенное что в эту степень — это нормализованноеfloat
,double
,long double
соответственноFLT_MAX_EXP
,DBL_MAX_EXP
,LDBL_MAX_EXP
— максимальное положительное целое число, такое, чтоFLT_RADIX
возведенное в степень на единицу меньше нормализованного числаfloat
,double
,long double
соответственноFLT_MAX_10_EXP
,DBL_MAX_10_EXP
,LDBL_MAX_10_EXP
— максимальное отрицательное целое число такое, что 10, возведенное что в эту степень — это нормализованноеfloat
,double
,long double
соответственноDECIMAL_DIG
(C99) — минимальное количество десятичных цифр такое, что любое число самого большого вещественного типа может быть представлено в десятичном виде с точностьюDECIMAL_DIG
цифр и переведено обратно в изначальный вещественный тип без изменения значения.DECIMAL_DIG
равен хотя бы 10.
Целые типы фиксированной длины[править | править код]
Стандарт C99 включает определения нескольких новых целочисленных типов для повышения переносимости программ.[2] Уже доступные целочисленные базовые типы были сочтены неудовлетворительными, так как их размер зависел от реализации. Новые типы находят широкое применение в встраиваемых системах. Все новые типы определены в заголовочном файле <inttypes.h>
(cinttypes
в C++) и также доступны в <stdint.h>
(cstdint
в C++). Типы можно разделить на следующие категории:
- Целые с точно заданным размером N бит в любой реализации. Включаются, только если доступны в реализации/платформе.
- Наименьшие целые, размер которых является минимальным в реализации, состоят минимум из N бит. Гарантируется что определены типы для N=8,16,32,64.
- Наибыстрейшие целые типы, которые являются гарантировано наиболее быстрыми в конкретной реализации, состоят минимум из N бит. Гарантируется что определены типы для N=8,16,32,64.
- Целые типы для указателей, которые гарантировано смогут хранить адрес в памяти. Включены, только если доступны на конкретной платформе.
- Наибольшие целые, размер которых является максимальным в реализации.
Следующая таблица показывает эти типы (N означает число бит):
Категория типа | Знаковые типы | Беззнаковые типы | ||||
---|---|---|---|---|---|---|
Тип | Минимальное значение | Максимальное значение | Тип | Минимальное значение | Максимальное значение | |
Точный размер | intN_t |
INTN_MIN |
INTN_MAX
|
uintN_t |
0 | UINTN_MAX
|
Минимальный размер | int_leastN_t |
INT_LEASTN_MIN |
INT_LEASTN_MAX
|
uint_leastN_t |
0 | UINT_LEASTN_MAX
|
Наибыстрый | int_fastN_t |
INT_FASTN_MIN |
INT_FASTN_MAX
|
uint_fastN_t |
0 | UINT_FASTN_MAX
|
Указатель | intptr_t |
INTPTR_MIN |
INTPTR_MAX
|
uintptr_t |
0 | UINTPTR_MAX
|
Максимальный размер | intmax_t |
INTMAX_MIN |
INTMAX_MAX
|
uintmax_t |
0 | UINTMAX_MAX
|
Спецификаторы формата для printf и scanf[править | править код]
Заголовочный файл <inttypes.h>
(cinttypes
в C++) расширяет возможности типов, определённых в <stdint.h>
. В них входят макросы, которые определяют спецификаторы типов для строки формата printf и scanf и несколько функций, которые работают с типами intmax_t
и uintmax_t
. Этот заголовочный файл был добавлен в C99.
- Строка формата printf
Макросы определены в формате PRI{fmt}{type}
. Здесь {fmt} означает формат вывода и принадлежит d
(десятичный), x
(шестнадцатиричный), o
(восьмеричный), u
(беззнаковый) или i
(целый). {type} определяет тип аргумента и принадлежит к N
, FASTN
, LEASTN
, PTR
либо MAX
, где N
означает число бит.
- Строка формата scanf
Макросы определены в формате SCN{fmt}{type}
. Здесь {fmt} означает формат вывода и принадлежит d
(десятичный), x
(шестнадцатиричный), o
(восьмиричный), u
(беззнаковый) или i
(целый). {type} определяет тип аргумента и принадлежит к N
, FASTN
, LEASTN
, PTR
либо MAX
, где N
означает число бит.
- Функции
Этот раздел не завершён. |
Структуры[править | править код]
Структуры в Си позволяют хранить несколько полей и в одной переменной. В других языках могут называться записями или кортежами. Например, данная структура хранит в себе имя человека и дату рождения:
struct birthday
{
char name[20];
int day;
int month;
int year;
};
Объявление структур в теле программы всегда должно начинаться с ключевого struct (необязательно в C++). Доступ к элементам структуры осуществляется с помощью оператора . или ->, если мы работаем с указателем на структуру. Структуры могут содержать указатели на самих себя, что позволяет реализовывать многие структуры данных, основанных на связных списках. Такая возможность может показаться противоречивой, однако все указатели занимают одинаковое число байт, поэтому размер этого поля не изменится от числа полей структуры.
Структуры не всегда занимают число байт, равное сумме байт их элементов. Компилятор обычно выравнивает элементы в блоки по 4 байта. Также есть возможность ограничить число бит, отводимое на конкретное поле, для этого надо после имени поля через двоеточие указать размер поля в битах. Такая возможность позволяет создавать битовые поля.
Некоторые особенности структур:
- Адрес памяти первого поля структуры равен адресу самой структуры
- Структуры могут быть инициализированы или приведены к какому-либо значению, с помощью составных литералов
- Пользовательские функции могут возвращать структуру, хотя часто не очень эффективны во время выполнения. С C99, структура может оканчиваться массивом переменного размера.
Массивы[править | править код]
Для каждого типа T, кроме void и типов функций, существует тип «массив из N элементов типа T». Массив — это коллекция значений одного типа, хранящихся последовательно в памяти. Массив размера N индексируется целым числом от 0 до N-1. Также возможны массивы, с неизвестным для компилятора размером. В роли размера массива должна выступать константа. Примеры
int cat[10] = {5,7,2}; // массив из 10 элементов, каждый типа int
int bob[]; // массив с неизвестным количеством элементов типа 'int'.
Массивы могут быть инициализированы с помощью списка инициализации, но не могут быть присвоены друг к другу. Массивы передаются в функции, с помощью указателя на первый элемент (имя массива и есть адрес первого элемента). Многомерные массивы являются массивами массивов. Примеры:
int a[10][8]; // массив из 10 элементов, каждый типа 'массив из 8 int элементов'
float f[][32] = {{0},{4,5,6}};
Типы указателей[править | править код]
Для любого типа T существует тип «указатель на T».
Переменные могут быть объявлены как указатели на значения различных типов с помощью символа *
. Для того чтобы определить тип переменной как указатель, нужно предварить её имя звёздочкой.
char letterC = 'C';
char *letter = &letterC; //взятие адреса переменной letterC и присваивание в переменную letter
printf("This code is written in %c.", *letter); //"This code is written in C."
Помимо стандартных типов, можно объявлять указатели на структуры и объединения:
struct Point { int x,y; } A;
A.x = 12;
A.y = 34;
struct Point *p = &A;
printf("X: %d, Y: %d", (*p).x, (*p).y); //"X: 12, Y: 34"
Для обращения к полям структуры по указателю существует оператор «стрелочка» ->
, синонимичный предыдущей записи: (*p).x
— то же самое, что и p->x
.
Поскольку указатель — тоже тип переменной, правило «для любого типа T» выполняется и для них: можно объявлять указатели на указатели. К примеру, можно пользоваться int***
:
int w = 100;
int *x = &w;
int **y = &x;
int ***z = &y;
printf("w contains %d.", ***z); //"w contains 100."
Существуют также указатели на массивы и на функции. Указатели на массивы имеют следующий синтаксис:
char *pc[10]; // массив из 10 указателей на char
char (*pa)[10]; // указатель на массив из 10 переменных типа char
pc
— массив указателей, занимающий 10 * sizeof(char*)
байт (на распространённых платформах — обычно 40 или 80 байт), а pa
— это один указатель; занимает он обычно 4 или 8 байт, однако позволяет обращаться к массиву, занимающему 10 байт: sizeof(pa) == sizeof(int*)
, но sizeof(*pa) == 10 * sizeof(char)
.
Указатели на массивы отличаются от указателей на первый элемент арифметикой. Например, если указатели pa
указывает на адрес 2000, то указатель pa+1
будет указывать на адрес 2010.
char (*pa)[10];
char array[10] = "Wikipedia";
pa = &array;
printf("An example for %s.\n", *pa); //"An example for Wikipedia."
printf("%c %c %c", (*pa)[1], (*pa)[3], (*pa)[7]); //"i i i"
Объединения[править | править код]
Объединения — это специальные структуры, которые позволяют различным полям разделять общую память. Таким образом в объединении может храниться только одно из полей. Размер объединения равен размеру наибольшего поля. Пример:
union
{
int i;
float f;
struct
{
unsigned int u;
double d;
} s;
} u;
В примере выше u
по размеру равна u.s
(размер которой является суммой u.s.u
и u.s.d
), так как s больше i
и f
. Чтение из объединения не включает преобразования типов.
Перечисления[править | править код]
Перечисления позволяют определять в коде пользовательские типы. Пример:
enum
{
red,
green=3,
blue
} color;
Перечисления улучшают читабельность кода, однако они не типобезопасны (например, для системы 3 и green одно и то же. В C++ для исправления этого недостатка были введены enum class), так как являются целочисленными. В данном примере значение red равно нулю, а значение blue четырём.
Указатели на функции[править | править код]
Указатели на функции позволяют передавать одни функции в другие и реализуют механизм обратного вызова. Указатели на функции позволяют ссылаться на функции с определённой сигнатурой. Пример создания указателя на функцию abs
, принимающую int и возвращающую int с именем my_int_f
:
int (*my_int_f)(int) = &abs;
// оператор & необязателен, но вносит ясность, явно показывая что мы передаём адрес
Указатели на функции вызываются по имени, как обычные вызовы функций. Указатели на функции отделены от обычных указателей и указателей на void.
Более сложный пример:
char ret_a(int x)
{
return 'a'+x;
}
typedef char (*fptr)(int);
fptr another_func(float a)
{
return &ret_a;
}
Здесь для удобства мы создали псевдоним с именем fptr для указателя на функцию, возвращающую char и принимающую int. Без typedef синтаксис был бы сложнее для восприятия:
char ret_a(int x)
{
return 'a'+x;
}
char (*func(float a, int b))(int)
{
char (*fp)(int) = &ret_a;
return fp;
}
char (*(*superfunc(double a))(float, int))(int)
{
char (*(*fpp)(float, int))(int)=&func;
return fpp;
}
Функция func возвращает не char, как может показаться, а указатель на ф-цию, возвращающую char и принимающую int. И принимает float и int.
Квалификаторы типов[править | править код]
Вышеупомянутые типы могут иметь различные квалификаторы типов. По стандарту C11, существует четыре квалификатора типа:
const
(C89) — означает что данный тип неизменяем после инициализации. (константа)volatile
(C89) — означает что значение данной переменной часто подвержено изменениям.restrict
(C99) — означает, что данный указатель адресует область памяти, на которую не ссылается никакой другой указатель._Atomic
(с C11) — означает, что данный тип является атомарным.[8] Также может именоватьсяatomic
, если подключитьstdatomic.h
.
Также с 99го стандарта был добавлен квалификатор для функций inline
, который является подсказкой компилятору, говорящей включить код из тела функции, вместо вызова самой функции.
Одной переменной могут принадлежать несколько квалификаторов. Пример:
const volatile int a = 5;
volatile int const * b = &a; //указатель на const volatile int
int * const c = NULL; // const указатель на int
Классы хранения[править | править код]
Также в Си существует четыре класса хранения:
auto
— по умолчанию для всех переменных.register
— подсказка компилятору хранить переменные в регистрах процессора. Для таких переменных отсутствует операция взятия адреса.static
— статические переменные. Имеют область видимости файла.extern
— переменные объявленные вне файла.
См. также[править | править код]
Примечания[править | править код]
- ↑ Barr, Michael Portable Fixed-Width Integers in C (2 декабря 2007). Дата обращения: 8 ноября 2011. Архивировано 9 октября 2010 года.
- ↑ 1 2 ISO/IEC 9899:1999 specification (неопр.). — С. 264, § 7.18 Integer types. Архивировано 15 августа 2011 года.
- ↑ 1 2 3 4 5 6 7 ISO/IEC 9899:1999 specification, TC3 (неопр.). — С. 22, § 5.2.4.2.1 Sizes of integer types
<limits.h>
. Архивировано 11 января 2018 года. - ↑ 1 2 3 4 Хотя стандартом установлен диапазон, вычисляемый по формуле от −(2n−1−1) до 2n−1−1, все известные компиляторы (gcc, clang и Microsoft compiler Архивная копия от 12 января 2016 на Wayback Machine) допускают диапазон от −(2n−1) до 2n−1−1, где n — разрядность типа.
- ↑ c - Зачем нужен тип long когда есть int? Stack Overflow на русском. Дата обращения: 11 марта 2020. Архивировано 27 февраля 2021 года.
- ↑ 1 2 Регистр формата влияет на регистр выводимых данных Например заглавные %E, %F, %G будут выводить нечисловые данные в верхнем регистре:
INF, NAN
иE
(экспонента). То же самое касается %x и %X - ↑ 64-Bit Programming Models: Why LP64? The Open Group. Дата обращения: 9 ноября 2011. Архивировано 25 декабря 2008 года.
- ↑ C11:The New C Standard Архивная копия от 12 июля 2018 на Wayback Machine, Thomas Plum