Название | 19 смертных грехов, угрожающих безопасности программ |
---|---|
Автор произведения | Майкл Ховард |
Жанр | Программирование |
Серия | |
Издательство | Программирование |
Год выпуска | 0 |
isbn | 5-9706-0027-X |
Оператор деления по модулю возвращает остаток от деления одного числа на другое, поэтому мы никогда не получим результат, который по абсолютной величине больше числителя. Ну и как тут может возникнуть переполнение? Переполнения как такового и не возникает, но результат может оказаться неожиданным из–за правил приведения. Рассмотрим 32–разрядное целое без знака, равное MAX_INT, то есть 0xffffffff, и 8–разрядное целое со знаком, равное–1. Остаток от деления–1 на 4 294 967 295 равен 1, не так ли? Не торопитесь. Компилятор желает работать с похожими числами, поэтому приведет–1 к типу unsigned int. Напомним, как это происходит. Сначала число расширяется со знаком до 32 битов, поэтому из 0xff получится 0xffffffff. Затем (int)(0xffffffff) преобразуется в (unsigned int)(0xffffffff). Как видите, остаток от деления–1 на 4 млрд равен нулю, по крайней мере, на нашем компьютере! Аналогичная проблема возникает при смешанной операции над любыми 32–или 64–разрядными целыми без знака и отрицательными целыми со знаком, причем это относится также и к делению, так что–1/4 294 967 295 равно 1, что весьма странно, ведь вы ожидали получить 0.
Операции сравнения
Ну уж сравнение на равенство–то должно работать, правда? Увы, если вы имеете дело с комбинацией целых со знаком и без знака, то таких гарантий никто не дает, по крайней мере в случае, когда знаковый тип шире беззнакового. Та же проблема, с которой мы столкнулись при рассмотрении деления и вычисления остатка, возникает и здесь и приводит к тем же последствиям.
Операции сравнения могут преподнести и другую неожиданность – когда максимальный размер сравнивается с числом со знаком. Противник может найти способ сделать это число отрицательным, а тогда оно заведомо будет меньше верхнего предела. Либо пользуйтесь числами без знака (это рекомендуемый способ), либо делайте две проверки: сначала проверяйте, что число больше или равно нулю, а потом – что оно меньше верхнего предела.
Поразрядные операции
Поразрядные операции AND, OR и XOR (исключающее или) вроде бы должны работать, но и тут расширение со знаком путает все карты. Рассмотрим пример:
int flags = 0x7f;
char LowByte = 0x80;
if ((char)flags ^ LowByte == 0xff)
return ItWorked;
Вам кажется, что результатом операции должно быть 0xff, именно с этим значением вы и сравниваете, но настырный компилятор решает все сделать по–своему и приводит оба операнда к типу int. Вспомните, мы же говорили, что даже для поразрядных операций выполняется приведение к int, если операнды имеют более узкий тип. Поэтому flags расширяется до 0x0000007f, и тут ничего плохого нет, зато LowByte расширяется до 0xffffff80, в результате операции мы получаем 0xffffffff!
Греховность С#
С# во многом похож на С++, что составляет его преимущество в случае, если вы знакомы