Я использую целые числа без знака, чтобы сделать мой код и его цель более понятными. Одна вещь, которую я делаю, чтобы защититься от неожиданных неявных преобразований при выполнении арифметики как со знаком, так и без знака, - это использовать беззнаковое короткое (обычно 2 байта) для моих беззнаковых переменных. Это эффективно по нескольким причинам:
- Когда вы выполняете арифметику с вашими беззнаковыми короткими переменными и литералами (которые имеют тип int) или переменными типа int, это гарантирует, что переменная без знака всегда будет повышаться до int перед вычислением выражения, поскольку int всегда имеет более высокий ранг, чем short , Это позволяет избежать любого непредвиденного поведения, выполняющего арифметику со знаковыми и беззнаковыми типами, при условии, что результат выражения вписывается в знаковое целое.
- В большинстве случаев переменные без знака, которые вы используете, не превысят максимального значения короткого байта без знака (65 535)
Общий принцип заключается в том, что тип переменных без знака должен иметь более низкий ранг, чем тип переменных со знаком, чтобы обеспечить переход к типу со знаком. Тогда у вас не будет неожиданного поведения переполнения. Очевидно, что вы не можете гарантировать это все время, но (чаще всего) это возможно.
Например, недавно у меня был цикл for что-то вроде этого:
const unsigned short cuint = 5;
for(unsigned short i=0; i<10; ++i)
{
if((i-2)%cuint == 0)
{
//Do something
}
}
Литерал '2' имеет тип int. Если бы я был unsigned int вместо unsigned short, то в подвыражении (i-2) 2 было бы переведено в unsigned int (поскольку unsigned int имеет более высокий приоритет, чем sign int). Если i = 0, то подвыражение равно (0u-2u) = некоторое массовое значение из-за переполнения. Та же идея с i = 1. Однако, так как я - беззнаковое короткое слово, оно получает тот же тип, что и литерал '2', который подписан как int, и все работает нормально.
Для дополнительной безопасности: в редком случае, когда архитектура, которую вы реализуете, приводит к тому, что значение int равно 2 байтам, это может привести к тому, что оба операнда в арифметическом выражении будут переведены в unsigned int, если неподписанная короткая переменная не подходит в подписанный 2-байтовый int, последний из которых имеет максимальное значение 32 767 <65 535. (См. Https://stackoverflow.com/questions/17832815/c-implicit-conversion-signed-unsigned для получения дополнительной информации). Чтобы избежать этого, вы можете просто добавить static_assert в вашу программу следующим образом:
static_assert(sizeof(int) == 4, "int must be 4 bytes");
и он не скомпилируется на архитектурах, где int составляет 2 байта.
for(unsigned int n = 10; n >= 0; n --)
(петли бесконечно)