Минимально адресуемым размером данных является, какправило, один байт (8 бит). Как правило - это значит, что не всегда, и бывают разные экзотические архитектуры, где "байт" - это 9 бит (PDP-10), или специализированные сигнальные процессоры с минимально адресуемым размером данных 16 бит (TMS32F28xx).
По стандарту языка Си определена константа CHAR_BIT
(в заголовочном файле <limits.h>
), для которой гарантируется, что CHAR_BIT >= 8
.
Тип данных, представляющий один байт, исторически называется "символ" - char
, который содержит ровно CHAR_BIT
количество бит.
Знаковость типа char
по стандарту не определена. Для
архитектуры x86 это знаковый тип данных, а, например,
для ARM - беззнаковый. Опции компилятора gcc -fsigned-char
и -funsigned-char
определяют это поведение.
Для остальных целочисленных типов данных: short
, int
,
long
, long long
, стандарт языка Си определяет минимальную разрядность:
Тип данных | Разрядность |
---|---|
short |
не менее 16 бит |
int |
не менее 16 бит, обычно 32 бит |
long |
не менее 32 бит |
long long |
не менее 64 бит, обычно 64 бит |
Таким образом, полагаться на количество разрядов в базовых
типах данных нельзя, и это нужно проверять с помощью
оператора sizeof
, который возвращает "количество байт", то
есть, в большинстве случает - сколько блоков размером
CHAR_BIT
помещается в типе данных.
С особой осторожностью нужно относиться к типу данных long
: на 64-разрядной системе Unix он является 64-битным, а, например, на 64-битной Windows - 32-битным. Поэтому, во избежание путаницы, использовать этот тип данных запрещено.
Перед целочисленными типами данных могут стоять модификаторы
unsigned
или signed
, которые указывают допустимость отрицательных чисел.
Для знаковых типов, старший бит определяет знак числа: значение 1
является признаком отрицательности.
Способ внутреннего представления отрицательных чисел не регламентирован стандартом языка Си, однако все современные компьютеры используют обратный дополнительный код. Более того, п.6.3.1.3.2 стандарта языка Си определяет способ приведения типов от знакового к беззнаковому таким способом, которые приводит к кодированию обратным дополнительным кодом.
Таким образом, значение -1
представляется как целое число,
все биты которого равны единице.
С точки зрения низкоуровневого программирования, и языка Си в частности, знаковость типов данных определяет только способ применения различных операций.
В заколовочных файлах файле <stdint.h>
(для Си99+) и
<cstdint>
(для C++11 и новее) определены типы данных, для
которых гарантируется фиксированное количесвто разрядов:
int8_t
, int16_t
, int32_t
, int64_t
, - для знаковых, и
uint8_t
, uint16_t
, uint32_t
, uint64_t
- для беззнаковых.
Ситуация целочисленного переполнения возникает, когда тип данных результата не имеет достаточно разрядов для того, чтобы хранить итоговый результат. Например, при сложении беззнаковых 8-разрядных целых чисел 255 и 1, получается результат, который не может быть представим 8-разрядным значением.
Для беззнаковых чисел ситуация переполнения является штатной, и эквивалентна операции "сложение по модулю".
Для знаковых типов данных - приводит к ситуации неопределенного поведения (Undefined Behaviour). В корректных программах такие ситуации встречаться не могут.
Пример:
int some_func(int x) {
return x+1 > x;
}
С точки зрения здравого смысла, такая программа должна всегда возвращать значение 1
(или true
), поскольку мы знаем, что x+1
всегда больше, чем x
. Компилятор может использовать этот факт для оптимизации кода, и всегда возвращать истинное значение. Таким образом, поведение программы зависит от того, какие опции оптимизации были использованы.
Свежие версии компиляторов clang
и gcc
(начиная с 6-й версии) умеют контролировать ситуации неопределенного поведения.
Можно включить генерацию управляемого кода программы, который использует дополнительные проверки во время выполнения. Естественно, это происходит ценой некоторого снижения производительности.
Такие инструменты называются ревизорами (sanitizers), предназначенными для разных целей.
Для включения ревизора, контролирующего ситуацию неопределенного поведения, используется опция -fsanitize=undefined
.
Целочисленное переполнение означает перенос старшего разряда, и многие процессоры, включая семейство x86, позволяют это диагностировать. Стандартами языков Си и C++ эта возможность не предусмотрена, однако компилятор gcc (начиная с 5-й версии) предоставляет нестандартные встроенные функции для выполнения операций с контролем переполнения.
// Операция сложения
bool __builtin_sadd_overflow (int a, int b, int *res);
bool __builtin_saddll_overflow (long long int a, long long int b, long long int *res);
bool __builtin_uadd_overflow (unsigned int a, unsigned int b, unsigned int *res);
bool __builtin_uaddl_overflow (unsigned long int a, unsigned long int b, unsigned long int *res);
bool __builtin_uaddll_overflow (unsigned long long int a, unsigned long long int b, unsigned long long int *res);
// Операция вычитания
bool __builtin_ssub_overflow (int a, int b, int *res)
bool __builtin_ssubl_overflow (long int a, long int b, long int *res)
bool __builtin_ssubll_overflow (long long int a, long long int b, long long int *res)
bool __builtin_usub_overflow (unsigned int a, unsigned int b, unsigned int *res)
bool __builtin_usubl_overflow (unsigned long int a, unsigned long int b, unsigned long int *res)
bool __builtin_usubll_overflow (unsigned long long int a, unsigned long long int b, unsigned long long int *res)
// Операция умножения
bool __builtin_smul_overflow (int a, int b, int *res)
bool __builtin_smull_overflow (long int a, long int b, long int *res)
bool __builtin_smulll_overflow (long long int a, long long int b, long long int *res)
bool __builtin_umul_overflow (unsigned int a, unsigned int b, unsigned int *res)
bool __builtin_umull_overflow (unsigned long int a, unsigned long int b, unsigned long int *res)
bool __builtin_umulll_overflow (unsigned long long int a, unsigned long long int b, unsigned long long int *res)