Interested Article - Система типов Си
- 2020-04-13
- 1
Система типов Си — реализация понятия типа данных в языке программирования Си . Сам язык предоставляет базовые арифметические типы, а также синтаксис для создания массивов и составных типов. Некоторые заголовочные файлы из стандартной библиотеки Си содержат определения типов с дополнительными свойствами .
Базовые типы
Язык Си предоставляет множество базовых типов. Большинство из них формируется с помощью одного из четырёх арифметических спецификаторов типа, (
char
,
int
,
float
и
double
), и опциональных спецификаторов (
signed
,
unsigned
,
short
и
long
). Хотя стандартом установлен диапазон, вычисляемый по формуле
от
−(2
n−1
−1)
до
2
n−1
−1
, все известные компиляторы (
gcc
,
clang
и Microsoft
) допускают диапазон
от
−(2
n−1
)
до
2
n−1
−1
, где n — разрядность типа.
В таблице ниже предполагается, что 1 байт = 8 битам.
На подавляющем большинстве современных платформ это так, однако возможна ситуация, когда 1 байт равняется 16 битам или какому-то другому числу, как правило степени двойки.
Тип | Пояснение | Спецификатор формата |
---|---|---|
char
|
Целочисленный, самый маленький из возможных адресуемых типов. Может содержать базовый набор символов. Может быть как знаковым, так и беззнаковым, зависит от реализации. Содержит
CHAR_BIT
(как правило, 8) бит.
|
%c
|
signed
char
|
Того же размера что и
char
, но гарантированно будет со знаком. Может принимать значения
как минимум
из диапазона
[−127, +127]
, обычно в реализациях
[−12
8
, +127]
|
%c
(также
%d
или
%hhi
(
%hhx
,
%hho
) для вывода в числовой форме)
|
unsigned
char
|
Того же размера что и
char
, но гарантированно без знака. Диапазон:
[0, 2
CHAR_BIT
− 1]
. Как правило,
[0, 255]
|
%c
(или
%hhu
для вывода в числовой форме)
|
short
short
int
signed
short
signed
short
int
|
Тип
короткого
целого числа со знаком. Может содержать числа
как минимум
из диапазона
[−32767, +32767]
, обычно в реализациях
[−3276
8
, +32767]
. Таким образом, это по крайней мере 16 бит (2 байта).
|
%hi
|
unsigned
short
unsigned
short
int
|
Такой же, как
short
, но беззнаковый. Диапазон:
[0, +65535]
|
%hu
|
int
signed
signed
int
|
Основной тип целого числа со знаком. Может содержать числа
как минимум
из диапазона
[−32767, +32767]
. Таким образом, это по крайней мере 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]
.
Таким образом, это по крайней мере 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]
.
Таким образом, это по крайней мере 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
|
Тип вещественного числа с плавающей запятой, обычно называемый типом числа двойной точности с плавающей запятой. На большинстве систем соответствует . |
%f
(
%F
)
(
|
long
double
|
Тип вещественного числа с плавающей запятой, обычно ставящийся в соответствие к формату числа
с плавающей запятой. В отличие от
float
и
double
, может быть 80-битным форматом с плавающей запятой, не-IEEE «double-double» или «IEEE 754 бинарный формат с плавающей запятой четырёхкратной точности». Если более точного формата не предоставлено, эквивалентен
double
. Смотрите
для подробностей.
|
%Lf
%LF
%Lg
%LG
%Le
%LE
|
Также не были упомянуты следующие спецификаторы типов: (
%s
для строк,
%p
для указателей,
%x
(
%X
) для шестнадцатеричного представления,
%o
для восьмеричного.
Реальный размер целочисленных типов зависит от реализации. Стандарт лишь оговаривает отношения в размерах между типами и минимальные рамки для каждого типа:
Так
long long
не должен быть меньше
long
, который в свою очередь не должен быть меньше
int
, который в свою очередь не должен быть меньше
short
. Так как
char
— наименьший из возможных адресуемых типов, другие типы не могут иметь размер меньше него.
Минимальный размер для
char
— 8 бит, для
short
и
int
— 16 бит, для
long
— 32 бита, для
long long
— 64 бита.
Желательно, чтобы тип
int
был таким целочисленным типом, с которым наиболее эффективно работает процессор. Это позволяет достигать высокой гибкости, например, все типы могут занимать 64 бита. Однако, есть популярные схемы, описывающие размеры целочисленных типов.
На практике это означает, что
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
включает определения нескольких новых целочисленных типов для повышения переносимости программ.
Уже доступные целочисленные базовые типы были сочтены неудовлетворительными, так как их размер зависел от реализации. Новые типы находят широкое применение в встраиваемых системах. Все новые типы определены в заголовочном файле
<inttypes.h>
(
cinttypes
в C++) и также доступны в
<stdint.h>
(
cstdint
в C++). Типы можно разделить на следующие категории:
- Целые с точно заданным размером N бит в любой реализации. Включаются, только если доступны в реализации/платформе.
- Наименьшие целые, размер которых является минимальным в реализации, состоят минимум из N бит. Гарантируется что определены типы для N=8,16,32,64.
- Наибыстрейшие целые типы, которые являются гарантировано наиболее быстрыми в конкретной реализации, состоят минимум из N бит. Гарантируется что определены типы для N=8,16,32,64.
- Целые типы для указателей, которые гарантировано смогут хранить адрес в памяти. Включены, только если доступны на конкретной платформе.
- Наибольшие целые, размер которых является максимальным в реализации.
Следующая таблица показывает эти типы ( N означает число бит):
Категория типа | Знаковые типы | Беззнаковые типы | ||||
---|---|---|---|---|---|---|
Тип | Минимальное значение | Максимальное значение | Тип | Минимальное значение | Максимальное значение | |
Точный размер |
int
N
_t
|
INT
N
_MIN
|
INT
N
_MAX
|
uint
N
_t
|
0 |
UINT
N
_MAX
|
Минимальный размер |
int_least
N
_t
|
INT_LEAST
N
_MIN
|
INT_LEAST
N
_MAX
|
uint_least
N
_t
|
0 |
UINT_LEAST
N
_MAX
|
Наибыстрый |
int_fast
N
_t
|
INT_FAST
N
_MIN
|
INT_FAST
N
_MAX
|
uint_fast
N
_t
|
0 |
UINT_FAST
N
_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
,
FAST
N
,
LEAST
N
,
PTR
либо
MAX
, где
N
означает число бит.
- Строка формата scanf
Макросы определены в формате
SCN
{fmt}{type}
. Здесь
{fmt}
означает формат вывода и принадлежит
d
(десятичный),
x
(шестнадцатиричный),
o
(восьмиричный),
u
(беззнаковый) или
i
(целый).
{type}
определяет тип аргумента и принадлежит к
N
,
FAST
N
,
LEAST
N
,
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.
Квалификаторы типов
Вышеупомянутые типы могут иметь различные квалификаторы типов. По стандарту , существует четыре квалификатора типа:
-
-
-
restrict
( C99 ) — означает, что данный указатель адресует область памяти, на которую не ссылается никакой другой указатель. -
_Atomic
(с C11 ) — означает, что данный тип является атомарным. Также может именоваться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 (2 декабря 2007). Дата обращения: 8 ноября 2011. 9 октября 2010 года.
- ↑ (неопр.) . — С. 264, § 7.18 Integer types . 15 августа 2011 года.
-
↑
(неопр.)
. — С. 22, § 5.2.4.2.1 Sizes of integer types
<limits.h>
. 11 января 2018 года. - ↑ Хотя стандартом установлен диапазон, вычисляемый по формуле от −(2 n−1 −1) до 2 n−1 −1 , все известные компиляторы ( gcc , clang и Microsoft от 12 января 2016 на Wayback Machine ) допускают диапазон от −(2 n−1 ) до 2 n−1 −1 , где n — разрядность типа.
- Stack Overflow на русском. Дата обращения: 11 марта 2020. 27 февраля 2021 года.
-
↑
Регистр формата влияет на регистр выводимых данных Например заглавные %E, %F, %G будут выводить нечисловые данные в верхнем регистре:
INF, NAN
иE
(экспонента). То же самое касается %x и %X - The Open Group. Дата обращения: 9 ноября 2011. 25 декабря 2008 года.
- от 12 июля 2018 на Wayback Machine , Thomas Plum
- 2020-04-13
- 1