|
Программирование >> Решение нетривиальных задач
Следующий похожий код просто печально отказывается работать без предупреждающего сообщения: some object array[ size ]; void foo( int x ); qsort( array, size, sizeof(some object), ((*)( void*, void*)) foo); Функция qsort() передает аргументы-указатели в foo(), но foo() ждет в качестве аргумента int, поэтому будет использовать значение указателя в качестве int. Дальше еще хуже - foo() вернет мусор, который будет использован qsort() , так как она ожидает в качестве возвращаемого значения int. Выравнивание также связано с затруднениями. Многие компьютеры требуют, чтобы объекты определенных типов располагались по особым адресам. Например, несмотря на то, что 1-байтоый тип char может располагаться в памяти по любому адресу, 2-байтовый short должен будет иметь четный адрес, а 4-байтовый long - четный и кратный четырем. Следующий код вновь не выдаст предупреждений, но может вызвать зависание компьютера во время выполнения: short x; long *lp = (long*)( &x ); *lp = 0; Эта ошибка особенно опасна, потому что *lp = 0 не сработает лишь тогда, когда x окажется по нечетному или не кратному четырем адресу. Может оказаться, что этот код будет работать до тех пор, пока вы не добавите объявление второй переменной типа short сразу перед x, после чего эта программа зависнет. Один из известных мне компиляторов пытается справиться с этой проблемой, фактически модифицируя содержимое указателя для того, чтобы гарантировать правильный адрес в качестве побочного эффекта от приведения типа. Другими словами, следующий код мог бы на самом деле модифицировать p: p = (char *)(long *); 71. Немедленно обрабатывайте особые случаи Пишите свои алгоритмы таким образом, чтобы они обрабатывали вырожденные случаи немедленно. Вот тривиальный пример того, как сделать это неправильно: print( const char *str ) 29 } Листинг 3 делает то же самое, но я модифицировал указатель на предыдущий элемент в структуре node, поместив туда адрес поля next предыдущего элемента вместо указателя на всю структуру. Это простое изменение означает, что первый элемент больше не является особым случаем, поэтому функция remove становится заметно проще. Смысл этого состоит в том, что незначительная переделка этой задачи позволяет мне использовать алгоритм, не имеющий особых случаев, этим if( !*str ) ничего не делать, строка пуста return; while( *str ) putchar( *str++ ); Оператор if тут не нужен, потому что этот случай хорошо обрабатывается циклом while. Листинги 2 и 3 демонстрируют более реалистический сценарий. Листинг 2 определяет умышленно наивный заголовок связанного списка и функцию для удаления из него элемента. Листинг 2. Связанный список: вариант 1 1 typedef struct node 3 struct node *next, *prev; 4 ... 5 } node; 7 node *head; 8 9 remove( node **headp, node *remove ) 10 { 11 Удалить элемент, на который указывает remove, из 12 списка, на начало которого указывает *headp. 14 if( *headp == remove ) Этот элемент в начале списка. 15 { 16 if ( remove->next ) Если это не единственн1й 17 remove->next->prev = NULL; элемент в списке, то 18 поместите следующий 19 за ним элемент первым 20 в списке. 21 *headp = remove->next; 22 } 23 else Элемент находится в середине списка 24 { 25 remove->prev->next = remove->next; 26 if( remove->next ) 27 remove->next->prev = remove->prev; 28 } самым упрощая программу. Конечно, эта простота не дается бесплатно - теперь стало невозможным перемещение по списку в обратном направлении - но нам ведь это может быть и не нужно. Листинг 3. Связанный список: вариант 2 1 typedef struct node 3 struct node *next, **prev; <== К prev добавлен символ * 5 } node; 7 node *head; 9 remove( node **headp, node *remove ) 10 { 11 if( *(remove->prev) = remove->next ) если не в конце 12 remove->next->prev = remove->prev; списка, то уточнить 13 следующий элемент 14 } 72. Не старайтесь порадовать lint Lint является программой проверки синтаксиса для языка Си. (Также имеется версия для Си++ в среде MS-DOS/Windows. Она выпускается фирмой Gimple Software). Хотя эти программы неоценимы при использовании время от времени, они выводят такую кучу сообщений об ошибках и предупреждений, что текст вашей программы будет почти невозможно прочитать, если вы попробуете избавиться от всех них. Оказывается, нужно избегать кода, подобного следующему: (void ) printf( ... ); Тут вообще нет ничего неверного в присваивании в цикле: while( p = f() ) g(p); даже если новичок в программировании может воспользоваться символом = вместо ==. (Проблемы новичков не должны приниматься во внимание, когда вы обсуждаете рекомендуемый стиль для опытных профессионалов. Это похоже на принятие закона, который требует, чтобы все велосипеды оснащались боковыми колесиками, потому что двухлетним малышам без них тяжело кататься). Я читал предложение поручать компилятору поиск небрежных присваиваний (когда вы на самом деле подразумевали сравнение) просто размещая константы в левой части. Например, следующий фрагмент даст ошибку при компиляции, если вы используете = вместо ==: #define MAX 100
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |