Заголовочный файл cstdint (stdint.h)
Заголовочный файл cstdint описывает целочисленные типы данных с установленными диапазонами представления чисел. Вместе с типами данных, в этом файле определены макросы с указанием верхних и нижних границ представляемых значений и макро-функции для формирования диапазонов представляемых значений для каждого типа данных. Ниже, в таблице, приводятся целочисленные типы данных и их описание.
Целочисленные типы данных
Целые типы данных имеют размеры 8, 16, 32 и 64 бит. Никаких других целочисленных типов данных с меньшим размером в С++ не существует. Ниже приведена таблица целочисленных типов данных, с константами и описанием.
Некоторые из этих типов данных могут обозначать одни и те же типы данных. Таким образом, перегруженные функции не должны полагаться на эти типы данных.
Обратите внимание, что некоторые типы данных не являются обязательными (и, следовательно, не могут быть переносимыми). Некоторые реализации библиотеки могут также определить дополнительные типы данных, с другой шириной диапазонов для совместимости со своей системой.
Ниже показаны ограничения типов данных заголовочного файла cstdint .
Макросы
| Макрос | Описание | Диапазон |
|---|---|---|
| INTMAX_MIN | Минимальное значение типа данных intmax_t | -(2 63 ), или меньше |
| INTMAX_MAX | Максимальное значение типа данных intmax_t | 2 63 -1, или больше |
| UINTMAX_MAX | Максимальное значение типа данных uintmax_t | 2 64 -1, или больше |
| INTN_MIN | Минимальное значение знакового типа данных N (8, 16, 32, 64) | -2 (N-1) |
| INTN_MAX | Максимальное значение знакового типа данных N (8, 16, 32, 64) | 2 (N-1) -1 |
| UINTN_MAX | Максимальное значение беззнакового типа данных N (8, 16, 32, 64) | 2 N -1 |
| INT_LEASTN_MIN | Минимальное значение знакового типа данных N, с минимальной шириной диапазона. | -(2 (N-1) -1), или меньше |
| INT_LEASTN_MAX | Максимальное значение знакового типа данных N, с минимальной шириной диапазона. | 2 (N-1) -1, или больше |
| UINT_LEASTN_MAX | Максимальное значение беззнакового типа данных N, с минимальной шириной диапазона. | 2 N -1, или больше |
| INT_FASTN_MIN | Минимальное значение быстрого знакового типа данных. | -(2 (N-1) -1), или меньше |
| INT_FASTN_MAX | Максимальное значение быстрого знакового типа данных. | 2 (N-1) -1, или больше |
| UINT_FASTN_MAX | Максимальное значение быстрого беззнакового типа данных. | 2 N -1, или больше |
| INTPTR_MIN | Минимальное значение intptr_t . | -(2 15 -1), или меньше |
| INTPTR_MAX | Максимальное значение intptr_t . | 2 15 -1, или больше |
| UINTPTR_MAX | Максимальное значение uintptr_t . | 2 16 -1, или больше |
Вместо N подставляйте количество битов — 8, 16, 32, 64.
Ограничение остальных целочисленных типов данных, показаны ниже.
Макросы границ целочисленных типов данных
| Макросы | Описание | Ширина диапазона |
|---|---|---|
| SIZE_MAX | Максимальное значение типа данных size_t | 2 64 -1, или больше |
| PTRDIFF_MIN | Минимальное значение типа данных ptrdiff_t | -(2 16 -1), или меньше |
| PTRDIFF_MAX | Максимальное значение типа данных ptrdiff_t | 2 16 -1, или больше |
| SIG_ATOMIC_MIN | Минимальное значение типа данных sig_atomic_t | если sig_atomic_t знаковый: -127, или больше. если sig_atomic_t беззнаковый: 0 |
| SIG_ATOMIC_MAX | Максимальное значение типа данных sig_atomic_t | если sig_atomic_t знаковый: 127, или больше если sig_atomic_t беззнаковый: 255, или меньше |
| WCHAR_MIN | Минимальное значение типа данных wchar_t | если wchar_t знаковый: -127, или меньше если wchar_t беззнаковый: 0 |
| WCHAR_MAX | Максимальное значение типа данных wchar_t | если wchar_t знаковый: 127, или больше если wchar_t беззнаковый: 255, или больше |
| WINT_MIN | Минимальное значение типа данных wint_t | если wint_t знаковый: -32767, или меньше если wchar_t беззнаковый: 0 |
| WINT_MAX | Максимальное значение типа данных wint_t | если wint_t знаковый: 32767, или больше если wchar_t беззнаковый: 65535, или больше |
Макро-функции
Эти функции преобразовывают передаваемые им значения в целочисленные константные выражения:
<cstdint> (stdint.h)
The following are typedefs of fundamental integral types or extended integral types.
| signed type | unsigned type | description |
|---|---|---|
| intmax_t | uintmax_t | Integer type with the maximum width supported. |
| int8_t | uint8_t | Integer type with a width of exactly 8, 16, 32, or 64 bits. For signed types, negative values are represented using 2’s complement. No padding bits. Optional: These typedefs are not defined if no types with such characteristics exist.* |
| int16_t | uint16_t | |
| int32_t | uint32_t | |
| int64_t | uint64_t | |
| int_least8_t | uint_least8_t | Integer type with a minimum of 8, 16, 32, or 64 bits. No other integer type exists with lesser size and at least the specified width. |
| int_least16_t | uint_least16_t | |
| int_least32_t | uint_least32_t | |
| int_least64_t | uint_least64_t | |
| int_fast8_t | uint_fast8_t | Integer type with a minimum of 8, 16, 32, or 64 bits. At least as fast as any other integer type with at least the specified width. |
| int_fast16_t | uint_fast16_t | |
| int_fast32_t | uint_fast32_t | |
| int_fast64_t | uint_fast64_t | |
| intptr_t | uintptr_t | Integer type capable of holding a value converted from a void pointer and then be converted back to that type with a value that compares equal to the original pointer. Optional: These typedefs may not be defined in some library implementations.* |
Some of these typedefs may denote the same types. Therefore, function overloads should not rely on these being different.
* Notice that some types are optional (and thus, with no portability guarantees). A particular library implementation may also define additional types with other widths supported by its system. In any case, if either the signed or the unsigned version is defined, both the signed and unsigned versions are defined.
Macros
Limits of cstdint types
| Macro | description | defined as |
|---|---|---|
| INTMAX_MIN | Minimum value of intmax_t | -(2 63 -1), or lower |
| INTMAX_MAX | Maximum value of intmax_t | 2 63 -1, or higher |
| UINTMAX_MAX | Maximum value of uintmax_t | 2 64 -1, or higher |
| INT N _MIN | Minimum value of exact-width signed type | Exactly -2 (N-1) |
| INT N _MAX | Maximum value of exact-width signed type | Exactly 2 (N-1) -1 |
| UINT N _MAX | Maximum value of exact-width unsigned type | Exactly 2 N -1 |
| INT_LEAST N _MIN | Minimum value of minimum-width signed type | -(2 (N-1) -1), or lower |
| INT_LEAST N _MAX | Maximum value of minimum-width signed type | 2 (N-1) -1, or higher |
| UINT_LEAST N _MAX | Maximum value of minimum-width unsigned type | 2 N -1, or higher |
| INT_FAST N _MIN | Minimum value of fastest minimum-width signed type | -(2 (N-1) -1), or lower |
| INT_FAST N _MAX | Maximum value of fastest minimum-width signed type | 2 (N-1) -1, or higher |
| UINT_FAST N _MAX | Maximum value of fastest minimum-width unsigned type | 2 N -1, or higher |
| INTPTR_MIN | Minimum value of intptr_t | -(2 15 -1), or lower |
| INTPTR_MAX | Maximum value of intptr_t | 2 15 -1, or higher |
| UINTPTR_MAX | Maximum value of uintptr_t | 2 16 -1, or higher |
Where N is one in 8, 16, 32, 64, or any other type width supported by the library.
Only the macros corresponding to types supported by the library are defined.
Are types like uint32, int32, uint64, int64 defined in any stdlib header?
I often see source code using types like uint32, uint64 and I wonder if they should be defined by the programmer in the application code or if they are defined in a standard lib header.
What’s the best way to have these types on my application source code?
4 Answers 4
The C99 stdint.h defines these:
- int8_t
- int16_t
- int32_t
- uint8_t
- uint16_t
- uint32_t
And, if the architecture supports them:
- int64_t
- uint64_t
There are various other integer typedefs in stdint.h as well.
If you’re stuck without a C99 environment then you should probably supply your own typedefs and use the C99 ones anyway.
Свод правил по работе с целыми числами в C/C++
В основу статьи легли мои собственные выработанные нелегким путем знания о принципах работы и правильном использовании целых чисел в C/C++. Помимо самих правил, я решил привести список распространенных заблуждений и сделать небольшое сравнение системы целочисленных типов в нескольких передовых языках. Все изложение строилось вокруг баланса между краткостью и полноценностью, чтобы не усложнять восприятие и при этом отчетливо передать важные детали.
Всякий раз, когда я читаю или пишу код на C/C++, мне приходится вспоминать и применять эти правила в тех или иных ситуациях, например при выборе подходящего типа для локальной переменной/элемента массива/поля структуры, при преобразовании типов, а также в любых арифметических операциях или сравнениях. Обратите внимание, что типы чисел с плавающей запятой мы затрагивать не будем, так как это большей частью относится к анализу и обработке ошибок аппроксимации, вызванных округлением. В противоположность этому, математика целых чисел лежит в основе как программирования, так и компьютерной науки в целом, и в теории вычисления здесь всегда точны (не считая проблем реализации вроде переполнения).
Типы данных
Базовые целочисленные типы
Целочисленные типы устанавливаются с помощью допустимой последовательности ключевых слов, взятых из набора
Несмотря на то, что битовая ширина каждого базового целочисленного типа определяется реализацией (т.е. зависит от компилятора и платформы), стандартом закреплены следующие их свойства:
- char : минимум 8 бит в ширину;
- short : минимум 16 бит и при этом не меньше char ;
- int : минимум 16 бит и при этом не меньше short ;
- long : минимум 32 бит и при этом не меньше int ;
- long long : минимум 64 бит и при этом не меньше long .
Наличие знака
- Стандартный сhar может иметь знак или быть беззнаковым, что зависит от реализации.
- Стандартные short , int , long и long long идут со знаком. Беззнаковыми их можно сделать, добавив ключевое слово unsigned .
- Числа со знаком можно кодировать в двоичном формате в виде дополнительного кода, обратного или как величину со знаком. Это определяется реализацией. Заметьте, что обратный код и величина со знаком имеют различные шаблоны битов для отрицательного нуля и положительного, в то время как дополнительный код имеет уникальный нуль.
- Символьные литералы (в одинарных кавычках) имеют тип ( signed ) int в C, но ( signed или unsigned ) char в C++.
Дополнительные правила
- sizeof(char) всегда равен 1, независимо от битовой ширины char .
- Битовая ширина не обязательно должна отличаться. Например, допустимо использовать char , short и int , каждый шириной в 32 бита.
- Битовая ширина должна быть кратна 2. Например, int может иметь ширину 36 бит.
- Есть разные способы написания целочисленного типа. К примеру, в каждой следующей строке перечислен набор синонимов:
- int , signed , signed int , int signed ;
- short , short int , short signed , short signed int ;
- unsigned long long , long unsigned int long , int long long unsigned .
Типы из стандартных библиотек
- size_t (определен в stddef.h) является беззнаковым и содержит не менее 16 бит. При этом не гарантируется, что его ширина будет как минимум равна int .
- ptrdiff_t (определен в stddef.h) является целочисленным типом со знаком. Вычитание двух указателей будет давать этот тип. При этом не стоит ожидать, что вычитание двух указателей даст int .
- В stdint.h определена конкретная ширина типов: uint8_t , int8_t , 16 , 32 и 64 . Будьте внимательны к операциям, подразумевающим продвижение типов. Например, uint8_t + uint8_t даст int (со знаком и шириной не менее 16 бит), а не uint8_t , как можно было предположить.
Преобразования
Представим, что значение исходного целочисленного типа нужно преобразовать в значение целевого целочисленного типа. Такая ситуация может возникнуть при явном приведении, неявном приведении в процессе присваивания или при продвижении типов.
Как происходит преобразование?
Главный принцип в том, что, если целевой тип может содержать значение исходного типа, то это значение семантически сохраняется.
- Когда исходный тип расширяется до целевого типа с аналогичной знаковой характеристикой (например, signed char -> int или unsigned short -> unsigned long ), каждое исходное значение после преобразования сохраняется.
- Даже если исходный и целевой типы имеют разные диапазоны, все значения в их пересекающейся части будут сохранены. Например, int , содержащий значение в диапазоне [0, 255] , будет без потерь преобразован в unsigned char .
- При преобразовании в беззнаковый тип новое значение равняется старому значению по модулю 2 целевая ширина в битах . Объяснение:
- Если исходный тип беззнаковый и шире целевого, тогда старшие биты отбрасываются.
- Если исходный тип имеет знак, тогда в процессе преобразования берется исходное значение, и из него/к нему вычитается/прибавляется 2 целевая ширина в битах до тех пор, пока новое значение не впишется в диапазон целевого типа. Более того, если число со знаком представлено в дополнительном коде, то в процессе преобразования старшие биты отбрасываются, как и в случае с беззнаковыми числами.
- Если исходное значение вписывается в диапазон целевого типа, тогда процесс преобразования (например, расширение знака) производит целевое значение, семантически равное исходному.
- Если же оно не вписывается, тогда поведение будет определяться реализацией и может вызвать исключение (к примеру, прерывание из-за переполнения).
Арифметика
Продвижение/преобразование
-
Унарный арифметический оператор применяется только к одному операнду. Примеры: — ,
- В реализации присутствуют 16-битный short и 24-битный int . Если переменные x и y имеют тип unsigned short , то операция x & y продвигает оба операнда до signed int .
- В реализации присутствуют 32-битный char и 32-битный int . Если переменные x и y имеют тип unsigned char , то операция x – y продвигает оба операнда до unsigned int .
- (long) + (long) → (long) ;
- (unsigned int) * (int) → (unsigned int) ;
- (unsigned long) / (int) → (unsigned long) ;
- если int является 32-битным, а long 64-битным: (unsigned int) % (long) → (long) ;
- если int и long оба являются 32-битными: (unsigned int) % (long) → (unsigned long) .
Неопределенное поведение
Знаковое переполнение:
- При выполнении арифметических операций над целочисленным типом переполнение считается неопределенным поведением (UB). Такое поведение может вызывать верные, несогласованные и/или неверные действия как сразу, так и в дальнейшем.
- При выполнении арифметики над беззнаковым целым (после продвижений и преобразований) любое переполнение гарантированно вызовет оборот значения. Например, UINT_MAX + 1 == 0 .
- Выполнение арифметики над беззнаковыми целыми фиксированного размера может привести к едва уловимым ошибкам. Например:
- Пусть uint16_t = unsigned short , и int равен 32-битам. Тогда uint16_t x=0xFFFF , y=0xFFFF , z=x*y ; x и y будут продвинуты до int , и x * y приведет к переполнению int , вызвав неопределенное поведение.
- Пусть uint32_t = unsigned char , и int равен 33-битам. Тогда uint32_t x=0xFFFFFFFF , y=0xFFFFFFFF , z=x+y ; x и y будут продвинуты до int , и x + y приведет к переполнению int , то есть неопределенному поведению.
- Чтобы обеспечить безопасную арифметику с беззнаковыми целыми, нужно либо прибавить 0U , либо умножить на 1U в качестве пустой операции. Например: 0U + x + y или 1U * x * y . Это гарантирует, что операнды будут продвинуты как минимум до ранга int и при этом останутся без знаков.
- Деление на нуль и остаток с делителем нуля также относятся к неопределенному поведению.
- Беззнаковое деление/остаток не имеют других особых случаев.
- Деление со знаком может вызывать переполнение, например INT_MIN / -1 .
- Остаток со знаком при отрицательных операндах может вызывать сложности, так как некоторые части являются однообразными, в то время как другие определяются реализацией.
- Неопределенным поведением считается битовый сдвиг (< < и >>) на размер, который либо отрицателен, либо равен или больше битовой ширины.
- Левый сдвиг беззнакового операнда (после продвижения/преобразования) считается определенным правильно и отклонений в поведении не вызывает.
- Левый сдвиг операнда со знаком, содержащего неотрицательное значение, вследствие которого 1 бит переходит в знаковый бит, является неопределенным поведением.
- Левый сдвиг отрицательного значения относится к неопределенному поведению.
- Правый сдвиг неотрицательного значения (в типе операнда без знака или со знаком) считается определенным правильно и отклонений в поведении не вызывает.
- Правый сдвиг отрицательного значения определяется реализацией.
Счетчик цикла
Выбор типа
Предположим, что у нас есть массив, в котором нужно обработать каждый элемент последовательно. Длина массива хранится в переменной len типа T0 . Как нужно объявить переменную счетчика цикла i типа T1 ?
- Самым простым решением будет использовать тот же тип, что и у переменной длины. Например:
- Говоря обобщенно, переменная счетчика типа T1 будет работать верно, если диапазон T1 будет являться (не строго) надмножетсвом диапазона T0 . Например, если len имеет тип uint16_t , тогда отсчет с использованием signed long (не менее 32 бит) сработает.
- Говоря же более конкретно, счетчик цикла должен просто покрывать всю фактическую длину. Например, если len типа int гарантированно будет иметь значение в диапазоне [3,50] (обусловленное логикой приложения), тогда допустимо отсчитывать цикл, используя char без знака или со знаком (в котором однозначно можно представить диапазон [0,127] ).
- Нежелательно использовать переменную длины и переменную счетчика с разной знаковостью. В этом случае сравнение вызовет неявное сложное преобразование, сопровождаемое характерными для платформы проблемами. К примеру, не стоит писать такой код:
Отсчет вниз
Для циклов, ведущих отсчет вниз, более естественным будет использовать счетчик со знаком, потому что тогда можно написать:
При этом для беззнакового счетчика код будет таким:
Примечание: сравнение i >= 0 имеет смысл только, когда i является числом со знаком, но всегда будет давать true , если оно будет беззнаковым. Поэтому, когда это выражение встречается в беззнаковом контексте, значит, автор кода скорее всего допустил ошибку в логике.
Заблуждения
Все пункты приведенного ниже списка являются мифами. Не опирайтесь на эти ложные убеждения, если хотите писать корректный и портируемый код.
