Когда вы используете арифметический оператор, операнды проходят два преобразования.
Целочисленные продвижения: Если int
может представлять все значения типа, то операнд переводится в int. Это относится как к short
, так и к unsigned short
на большинстве платформ. Преобразование, выполненное на этом этапе, выполняется для каждого операнда отдельно, без учета другого операнда. (Есть еще правила, но это то, что применимо.)
Обычные арифметические преобразования: Если вы сравниваете unsigned int
с signed int
, поскольку ни один не включает весь диапазон другого, и оба имеют одинаковый ранг, то оба преобразуются в unsigned
тип. Это преобразование выполняется после проверки типа обоих операндов.
Очевидно, что «обычные арифметические преобразования» не всегда применяются, если нет двух операндов. Вот почему существует два набора правил. Одна ошибка, например, заключается в том, что операторы сдвига <<
и >>
не выполняют обычных арифметических преобразований, поскольку тип результата должен зависеть только от левого операнда (поэтому, если вы видите кого-то типа x << 5U
, U
означает «ненужный»).
Разбивка: Предположим, что типичная система с 32-битным int и 16-битным коротким.
int a = -1; // "signed" is implied
unsigned b = 2; // "int" is implied
if (a < b)
puts("a < b"); // not printed
else
puts("a >= b"); // printed
- Сначала добавляются два операнда. Так как оба
int
или unsigned int
, рекламные акции не проводятся.
- Затем два операнда преобразуются в один и тот же тип. Поскольку
int
не может представлять все возможные значения unsigned
, а unsigned
не может представлять все возможные значения int
, очевидного выбора нет. В этом случае оба преобразуются в unsigned
.
- При преобразовании из знака в без знака 2 32 многократно добавляется к знаковому значению до тех пор, пока оно не окажется в диапазоне беззнакового значения. На самом деле это просто шумиха, что касается процессора.
- Таким образом, сравнение становится
if (4294967295u < 2u)
, что неверно.
Теперь давайте попробуем это с short
:
short c = -1; // "signed" is implied
unsigned short d = 2;
if (c < d)
puts("c < d"); // printed
else
puts("c >= d"); // not printed
- Во-первых, два операнда повышаются. Поскольку оба могут быть достоверно представлены
int
, оба повышаются до int
.
- Затем они преобразуются в тот же тип. Но они уже одного типа,
int
, поэтому ничего не делается.
- Таким образом, сравнение становится
if (-1 < 2)
, что верно.
Написание хорошего кода: Есть простой способ поймать эти "ошибки" в вашем коде. Просто всегда компилируйте с включенными предупреждениями и исправляйте предупреждения. Я склонен писать такой код:
int x = ...;
unsigned y = ...;
if (x < 0 || (unsigned) x < y)
...;
Вы должны следить за тем, чтобы ни один код, который вы пишете, не попадал в другую подписанную и неподписанную информацию: переполнение со знаком. Например, следующий код:
int x = ..., y = ...;
if (x + 100 < y + 100)
...;
unsigned a = ..., b = ...;
if (a + 100 < b + 100)
...;
Некоторые популярные компиляторы оптимизируют (x + 100 < y + 100)
до (x < y)
, но это история для другого дня. Только не переполняйте свои подписанные номера.
Сноска. Обратите внимание, что хотя signed
подразумевается для int
, short
, long
и long long
, оно НЕ подразумевается для char
. Вместо этого это зависит от платформы.