19 смертных грехов, угрожающих безопасности программ - стр. 25
int flags = 0x7f;
char LowByte = 0x80;
if ((char)flags ^ LowByte == 0xff)
return ItWorked;
Вам кажется, что результатом операции должно быть 0xff, именно с этим значением вы и сравниваете, но настырный компилятор решает все сделать по–своему и приводит оба операнда к типу int. Вспомните, мы же говорили, что даже для поразрядных операций выполняется приведение к int, если операнды имеют более узкий тип. Поэтому flags расширяется до 0x0000007f, и тут ничего плохого нет, зато LowByte расширяется до 0xffffff80, в результате операции мы получаем 0xffffffff!
Греховность С#
С# во многом похож на С++, что составляет его преимущество в случае, если вы знакомы с C/C++. Но это же и недостаток, так как для С# характерны многие из проблем, присущих С++. Один любопытный аспект С# заключается в том, что безопасность относительно типов проверяется гораздо строже, чем в C/C++. Например, следующий код не будет компилироваться:
byte a, b;
a = 255;
b = 1;
byte c = (b + a);
error CS0029: Cannot implicitly convert type 'int' to 'byte'
(ошибка CS0029: Не могу неявно преобразовать тип 'int' в 'byte')
Если вы понимаете, о чем говорит это сообщение, то подумайте о возможных последствиях такого способа исправления ошибки:
byte с = (byte) (Ь + а) ;
Безопаснее воспользоваться классом Convert:
byte d = Convert.ToByte(a + b);
Поняв, что пытается сказать компилятор, вы хотя бы задумаетесь, есть ли в вашем коде реальная проблема. К сожалению, возможности компилятора ограничены. Если бы в предыдущем примере вы избавились от ошибки, объявив a, b и с как целые со знаком, то появилась бы возможность переполнения, а компилятор ничего не сказал бы.
Еще одна приятная особенность С# состоит в том, что он по мере необходимости пользуется 64–разрядными целыми числами. Например, следующий код дал бы неверный результат на С, но правильно работает на С#:
int i = -1;
uint j = 0xffffffff; // наибольшее положительное 32-разрядное целое
if(i == j)
Console.WriteLine("Отлично!");
Причина в том, что С# приведет операнды к типу long (64–разрядное целое со знаком), который позволяет точно сохранить оба числа. Если вы решите пойти дальше и проделать то же самое с числами типа long и ulong (в С# оба занимают 64 разряда), то компилятор сообщит, что необходимо явно преобразовать их к одному типу. По мнению авторов, стандарт C/C++ следует уточнить: если компилятор поддерживает операции над 64–разрядными значениями, то он должен в этом отношении вести себя так же, как С#.
Ключевые слова checked и unchecked
В языке С# есть ключевые слова checked и unchecked. Можно объявить checked–блок:
byte a = 1;
byte b = 255;
checked
{
byte c = (byte)(a + b);
byte d = Convert.ToByte(a + b);
Console.Write("{0} {1}\n", b+1, c);
}
В данном примере приведение а + b от int к byte возбуждает исключение. В следующей строке, где вызывается Convert.ToByte, исключение возникло бы и без ключевого слова checked, но его наличие приводит к возбуждению исключения еще и при вычислении аргументов метода Console.Write(). Поскольку иногда переполнение целого допускается намеренно, то имеется также ключевое слово unchecked, отключающее контроль на переполнение.
Слова checked и unchecked можно также использовать для включения или отключения контроля в одном выражении:
checked (с = (byte) (Ь + а));