Правила программирования на С и С++. Главы 1-6
Страница 73. Компьютеры не знают математики


 

66. Компьютеры не знают математики.

Компьютеры - это арифметические инструменты, славные счетные машины. Они не знают математики. Поэтому даже такие простые выражения, как следующее, могут добавить вам хлопот:

int x = 32767;

x = (x * 2) / 2;

(На 16-разрядной машине x получится равным -1. 32767 - это 0x7fff. Умножение на 2 - на самом деле сдвиг влево на один бит, дает в результате 0xfffe - отрицательное число. Деление на два является арифметическим сдвигом вправо с гарантированным знаковым расширением, и так вы получаете теперь 0xffff или -1). Поэтому важно каждый раз при выполнении арифметических вычислений учитывать ограничения компьютерной системы. Если вы производите умножение перед делением, то при этом рискуете выйти за пределы разрядности при сохранении результата; если вы сначала делите, то рискуете случайно округлить результат до нуля; и так далее. Численным методам для компьютеров посвящены целые книги, и вам нужно прочитать хотя бы одну из них, если в ваших программах много математики.

Вы также должны знать мелкие грехи своего языка программирования. В С, например, преобразование типов выполняется по принципу "оператор за оператором". Я однажды потратил утро, пытаясь разобраться, почему следующий код ничего не делает:

long x;

x ?= 0xffff; // очистить все, кроме младших 16-ти бит 32-битного типа long.

Компьютер имел 16-битовый тип int и 32-битовый тип long. Константа 0xffff типа int с арифметическим значением -1. Компилятор С при трансляции ?= обнаруживал разные типы операндов и поэтому преобразовывал int в long. -1 в типе long представляется как 0xffffffff, поэтому логическая операция И не имела эффекта. Это как раз тот способ, которым и должен работать данный язык программирования. Я просто об этом не подумал.

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

x ?= (long)0xffff;Единственным методом решения этой проблемы является: x ?= 0xffffUL;

или равноценный ему.

66.1. Рассчитывайте на невозможное.

Оператор switch всегда должен иметь предложение с ключевым словом default для ситуации по умолчанию, особенно если эта ситуация не должна возникать:

f( int i ) // переменная i должна иметь значение 1 или 2.

{

switch( i )

{

case 1: сделать_нечто(); break;

case 2: сделать_нечто_другое(); break;

default:

fprintf(stderr, "Внутренняя ошибка в f(): неверное значение i (%d)", i );

exit( -1 );

}

}

То же самое относится к блокам if/else, работающим в манере, схожей с оператором switch.

В цикле также нужна проверка на невероятное. Следующий фрагмент работает, даже если i первоначально равно 0 - чего по идее быть не должно:

f( int i ) // переменная i должна быть положительной

{

while ( --i >= 0 )

сделать_нечто();

}

Конструкция while(--i) менее надежна, так как она дает ужасный сбой в случае, если i сначала равно 0.

66.2. Всегда проверяйте код, вызывающий ошибку.

Это должно быть очевидно, но комитет ISO/ANSI по С++ потребовал, чтобы оператор new вызывал исключение, если он не смог выделить память, потому что было установлено, что удивительное множество ошибок во время выполнения в реальных программах вызвано тем, что люди не потрудились проверить, не возвратил ли new значение NULL.

Мне также довелось видеть множество программ, в которых люди не позаботились посмотреть, сработала ли функция fopen(), перед тем как начать пользоваться указателем FILE.

 
« Предыдущая статья