Те, кто успел самостоятельно откомпилировать пример Compare1, могли заметить предупреждение компилятора на строке со сравнением: "Comparing signed and unsigned types — widened both operands". Это предупреждение все объясняет: компилятор, зная, что совпадение наборов битов не гарантирует равенство знакового и беззнакового выражения, сначала "расширяет" типы выражений до того типа, чей диапазон целиком вмещает оба требуемых диапазона и лишь затем выполняет сравнение. Это обеспечивает правильный результат сравнения, но требует дополнительных ресурсов, поэтому компилятор выдает предупреждение.
Аналогичные действия компилятор выполнит при сравнении выражений типов Word
и SmallInt
, а также LongInt
и LongWord
. Тип Int64 не имеет беззнакового аналога, поэтому операнды этого типа при сравнении компилятор не "расширяет".
Явное приведение типов позволяет избавиться от операций по расширению типа и ограничиться побитовым сравнением (листинг 3.7. пример Compare2 на компакт-диске).
procedure TForm1.Button1Click(Sender: TObject);
var
X: Byte;
Y: ShortInt;
begin
Y := -1;
X := Y;
if X = Byte(Y) then Label1.Caption := 'Равно'
else Label1.Caption := 'Не равно';
end;
При компиляции такого кода не выдается никаких предупреждений, но и результат сравнения будет неверным: Равно.
Операции >, <, >= и <= тоже работают по-разному для знаковых и беззнаковых чисел. Пусть, например, сравниваются числа 01000000 и 11000000. В беззнаковом формате это 64 и 192, поэтому первое число меньше второго. А в знаковом это 64 и -64, т. е. первое число больше. Из-за этого для операций сравнения для знаковых и беззнаковых чисел в системе команд процессора существуют разные команды. В литературе, чтобы подчеркнуть это, часто используются различные названия операций в зависимости от формата: для знаковых чисел — "больше" и "меньше", для беззнаковых — "выше" и "ниже".
3.1.5. Неявное преобразование в цикле
Рассмотрим программу (пример ForRange на компакт-диске), на форме которой находятся кнопка и панель, причем кнопка (это важно!) — не на панели, а на форме, а на панели нет никаких компонентов. Обработчик нажатия на кнопку выглядит следующим образом (листинг 3.8).
procedure TForm1.Button1Click(Sender: TObject);
var
I: Cardinal;
begin
for I := 0 to Panel1.ControlCount - 1 do
Panel1.Controls[I].Tag := 1;
end;
На первый взгляд кажется, что при нажатии на кнопку ничего не должно происходить, т.к. на панели никаких визуальных компонентов нет, и цикл for
не должен выполниться ни разу. Тем не менее нажатие на кнопку вызывает исключение Access violation.
При нулевом количестве компонентов на панели выражение Panel1.ControlCount - 1
должно давать значение -1. Но поскольку переменная цикла имеет тип Сardinal
, эта комбинация битов интерпретируется как 4 294 967 295, верхняя граница оказывается больше нижней, и цикл начинает выполняться, обращаясь к несуществующим элементам управления. Отсюда и ошибка.
Ошибка исчезнет, если тип переменной I
изменить на Integer
— в этом случае верхняя граница цикла получит корректное значение -1, и цикл действительно не выполнится ни разу. Если на панели будет находиться хотя бы один компонент, ошибки тоже не будет, потому что верхняя граница цикла не выйдет из диапазона неотрицательных чисел.
Получается, что в ситуации, когда использование беззнакового типа кажется вполне безобидным (действительно, индексы списка не могут быть отрицательными), нас подстерегает "подводный камень", связанный с тем. что верхняя граница цикла может оказаться отрицательной и будет неявно приведена к большому положительному числу. Поэтому в цикле предпочтительнее переменная знакового типа Integer
, а если по каким-то причинам приходится использовать переменную типа Cardinal
, то необходимо внимательно следить за тем, какие значения принимают границы типа.