19 смертных грехов, угрожающих безопасности программ - страница 25




При выполнении встроенных операций над целыми типами переполнение или потеря значимости не индицируются. Единственные арифметические операторы, которые могут возбудить исключение (§11), – это оператор целочисленного деления / (§15.17.2) и оператор вычисления остатка % (§15.17.3). Они возбуждают исключение ArithmeticException, если правый операнд равен нулю.

В отличие от Visual Basic, Java поддерживает лишь подмножество всего диапазона целочисленных типов. Хотя 64–разрядные целые поддерживаются, но единственным беззнаковым типом является char, и он представляется в виде 16–разрядного значения без знака.

Поскольку в Java есть только знаковые типы, проверка переполнения становится непростым делом, и по сравнению с C/C++ удается лишь избежать затруднений, связанных со смешанными операциями над знаковыми и беззнаковыми величинами.

Греховность Perl

По крайней мере, два автора этой книги являются горячими сторонниками Perl. Но, несмотря на это, следует признать, что работа с целыми числами реализована в Perl странно. Внутри они представляются в виде чисел с плавающей точкой двойной точности, но тестирование позволяет выявить некоторые любопытные вещи. Рассмотрим следующий код:

$h = 4294967295;

$i = 0xffffffff;

$k = 0x80000000;

print "$h = 4294967295 – $h + 1 = ".($h + 1)."\n";

print "$i = 0xffffffff – $i + 1 = ".($i + 1)."\n";

printf("\nИспользуется printf со спецификатором %%d\n");

printf("\\$i = %d, \$i + 1 = %d\n\n", $i, $i + 1);

printf("\nТестируется граничный случай деления\n");

printf("0x80000000/-1 = %d\n", $k/-1);

print "0x80000000/-1 = ".($k/-1)."\n";

В результате печатается следующее:

[e:\projects\19_sins\perl foo.pl

4294967295 = 4294967295 – 4294967295 + 1 = 4294967296

4294967295 = 0xffffffff – 4294967295 + 1 = 4294967296

Используется printf со спецификатором %d

$i = -1, $i + 1 = -1

Тестируется граничный случай деления

0x80000000/-1 = -2147483648

0x80000000/-1 = -2147483648

На первый взгляд, результат выглядит странно, особенно когда используется printf с форматной строкой (в отличие от обычной функции print). Первым делом в глаза бросается то, что мы можем присвоить переменной максимально возможное значение без знака, но после прибавления к нему 1 она либо увеличивается на единицу, либо – если печатать с помощью %d – вообще не изменяется. Загвоздка в том, что на самом деле вы оперируете числами с плавающей точкой, а спецификатор %d заставляет Perl преобразовать double в int. В действительности никакого переполнения нет, но при печати результатов создается впечатление, будто оно произошло.

В силу особенностей работы с числами в Perl мы рекомендуем быть очень осторожными при написании на этом языке приложений, в которых много математических операций. Если вы не разбираетесь досконально в арифметике с плавающей точкой, то можете столкнуться с весьма поучительными сюрпризами. Другие языки высокого уровня, например Visual Basic, тоже иногда производят преобразования в числа с плавающей точкой. Следующий код иллюстрирует, что в таком случае происходит:

print (5/4)."\n";

1.25

Для большинства обычных приложений Perl ведет себя предсказуемо и работает великолепно. Но не забывайте, что вы имеете дело не с целыми числами, а с числами с плавающей точкой, – а это «две большие разницы».

Где искать ошибку

Любое приложение, в котором производятся арифметические операции, подвержено этому греху, особенно когда некоторые входные данные поступают от пользователя и их правильность не проверяется. Особое внимание обращайте на вычисление индексов массивов и размеров выделяемых буферов в программах на C/C++.