Skip to content

Latest commit

 

History

History
 
 

integers

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

Целочисленная арифметика

Целочисленные типы данных

Минимально адресуемым размером данных является, какправило, один байт (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)