Шрифт:
Интервал:
Закладка:
Общие методы отладки
Существует несколько разных подходов к отладке и тестированию типовой программы Linux. Обычно разработчик запускает программу и смотрит, что происходит. Если программа не работает, необходимо решить, что с ней делать. Можно изменить программу и попробовать снова (анализ программного кода, метод проб и ошибок), можно попытаться получить больше информации о том, что происходит внутри программы (оснащение контрольными средствами) или можно непосредственно проанализировать работу программы (контролируемое выполнение). Отладка включает в себя пять следующих этапов:
□ тестирование — поиск существующих изъянов или ошибок;
□ стабилизация — обеспечение повторяемости ошибок;
□ локализация — определение строки кода, отвечающей за ошибку;
□ корректировка — исправление программного кода;
□ проверка — подтверждение того, что исправление работает.
Программа с ошибками
Давайте рассмотрим пример программы, содержащей ошибки. Читая данную главу, вы будете пробовать отладить эту программу. Она написана во время разработки большой программной системы. Ее задача — протестировать единственную функцию sort, которая предназначена для реализации сортировки массива структур типа item методом "пузырька". Элементы сортируются по возрастанию поля key. Программа вызывает функцию sort для сортировки контрольного примера, чтобы протестировать функцию. В реальной жизни вы никогда не стали бы обращаться к этому конкретному алгоритму из-за его очень низкой эффективности. Мы же применяем его, потому что он короткий, относительно простой и его легко превратить в неправильный. На самом деле в стандартной библиотеке языка С есть функция с именем qsort, выполняющая эту задачу.
К сожалению, исходный код программы нелегко читается, в нем нет комментариев, и автор уже недоступен. Вам придется биться с ней самостоятельно, начиная с основной подпрограммы debug1.c.
/* 1 */ typedef struct {
/* 2 */ char *data;
/* 3 */ int key;
/* 4 */ } item;
/* 5 */
/* 6 */ item array[] = {
/* 7 */ {"bill", 3},
/* 8 */ {"neil", 4},
/* 9 */ {"john", 2},
/* 10 */ {"rick", 5},
/* 11 */ {"alex", 1},
/* 12 */ };
/* 13 */
/* 14 */ sort(a, n)
/* 15 */ item *a;
/* 16 */ {
/* 17 */ int i = 0, j = 0;
/* 18 */ int s = 1;
/* 19 */
/* 20 */ for(; i < n && s != 0; i++) {
/* 21 */ s = 0;
/* 22 */ for(j = 0; j < n; j++) {
/* 23 */ if(a[j].key > a[j + 1].key) {
/* 24 */ item t = a[j];
/* 25 */ a[j] = a[j+1];
/* 26 */ a[j+1] = t;
/* 27 */ s++;
/* 28 */ }
/* 29 */ }
/* 30 */ n--;
/* 31 */ }
/* 32 */ }
/* 33 */
/* 34 */ main()
/* 35 */ {
/* 36 */ sort(array,5);
/* 37 */ }
Теперь попытайтесь откомпилировать эту программу:
$ сс -о debug1 debug1.с
Она компилируется успешно без каких-либо сообщений об ошибках или предупреждений.
Прежде чем выполнять эту программу, вставьте фрагмент кода для вывода результата. В противном случае вы не будете знать, отработала ли программа. Вы добавите несколько дополнительных строк для отображения массива после сортировки. Назовите новую версию debug2.c.
/* 33 */ #include <stdio.h>
/* 34 */ main()
/* 35 */ {
/* 36 */ int i;
/* 37 */ sort(array, 5);
/* 38 */ for(i = 0; i < 5; i++)
/* 39 */ printf("array[3d] = (%s, %d)n",
/* 40 */ i, array[i].data, array[i].key);
/* 41 */ }
Этот дополнительный код, строго говоря, не является частью, позже добавленной программистом. Мы заставили вас добавить его только для тестирования программы. Следует быть очень внимательным, чтобы не внести новых ошибок в ваш тестовый код. Теперь снова откомпилируйте программу и на этот раз выполните ее:
$ cc -о debug2 debug2.с
$ ./debug2
Что произойдет, когда вы сделаете это, зависит от вашей версии Linux (или UNIX) и особенностей ее установки. В своих системах мы получили следующий результат:
array[0] = {john, 2}
array[1] = {alex, 1}
array[2] = {(null), -1}
array[3] = {bill, 3}
array[4] = {neil, 4}
В еще одной системе (запускающей другое ядро Linux) мы получили следующий вывод:
Segmentation fault
В вашей системе Linux вы увидите один из приведенных результатов или совсем другой. Мы рассчитывали получить приведенный далее вывод:
array[0] = {alex, 1}
array[1] = {john, 2}
array[2] = {bill, 3}
array[3] = {neil, 4}
array[4] = {rick, 5}
Ясно, что в данном программном коде есть серьезная ошибка. Он не выполняет сортировку корректно, если вообще работает, а если он завершается с ошибкой сегментации, то операционная система посылает сигнал программе, сообщая о том, что обнаружен несанкционированный доступ к памяти, и преждевременно завершает программу, чтобы не испортить данные в оперативной памяти.
Способность операционной системы обнаружить несанкционированный доступ к памяти зависит от настройки оборудования и некоторых тонкостей реализации системы управления памятью. В большинстве систем объем памяти, выделяемый программе операционной системой, больше реально используемого. Если несанкционированный доступ осуществляется к этому участку памяти, оборудование может не выявить несанкционированный доступ. Вот почему не все версии Linux и UNIX сгенерируют сигнал о нарушении сегментации.
ПримечаниеНекоторые библиотечные функции, такие как printf, в определенных обстоятельствах также будут препятствовать некорректному доступу, например при использовании указателя null.
Когда вы исследуете проблемы доступа к элементам массива, часто полезно увеличить размер этих элементов, поскольку это увеличит размер ошибки. Если вы читаете единственный байт за пределами массива байтов, это может вам сойти с рук, т.к. память, выделенная программе, будет округляться до величины, зависящей от операционной системы, возможно, равной 8 Кбайт.
Если вы увеличите размер элемента массива, заменив элемент типа item массивом из 4096 символов, любое обращение к несуществующему элементу массива, возможно, окажется за пределами выделенной памяти. Каждый элемент массива равен 4 Кбайт, поэтому некорректно используемый участок памяти будет находиться за концом массива на расстоянии от 0 до 4 Кбайт.
Если мы внесем эту поправку, назвав результат debug3.c, то получим ошибку сегментации в версиях Linux обоих авторов.
/* 2 */ char data[4096];
$ сс -о debug3 debug3.с
$ ./debug3
Segmentation fault
Возможно, что какие-то варианты систем Linux или UNIX все еще не будут выдавать сообщение об ошибке сегментации. Когда стандарт ANSI С утверждает, что поведение не определено, на самом деле он разрешает программе делать все, что угодно. Это выглядит так, как будто мы написали не удовлетворяющую стандартам программу на языке С, и она может демонстрировать очень странное поведение! Как видите, изъян в программе переводит ее в категорию программ с непредсказуемым поведением.
Анализ кода
Как мы упоминали ранее, часто, если программа не работает, как ожидалось, неплохо перечитать ее. Предположим, что мы просмотрели программный код примера этой главы и исправили в нем все очевидные ошибки.
ПримечаниеАнализ кода — это термин, применяемый для обозначения более формального процесса, в ходе которого группа разработчиков тщательно просматривает несколько сотен строк программного кода, но масштаб не имеет значения, это все равно анализ кода, и он остается очень полезным методом поиска ошибок.
Существуют средства, которые могут помочь в анализе кода, одно из самых очевидных — компилятор. Он сообщит вам о любых имеющихся в вашей программе синтаксических ошибках.
ПримечаниеУ некоторых компиляторов есть опции, формирующие предупреждения в сомнительных случаях, таких как отсутствие инициализации переменных или применение присваиваний в условиях. Например, компилятор GNU можно запускать со следующими опциями:
gcc -Wall -pedantic -ansi
Они порождают много предупреждений и дополнительных проверок на соответствие стандартам языка С. Рекомендуем взять за правило использование этих опций, особенно Wall. Она генерирует полезную информацию при обнаружении ошибок в программе.
Чуть позже мы кратко обсудим и другие средства, lint и splint. Как и компилятор, они анализируют код и сообщают о фрагментах кода, которые могут быть некорректными.
- Linux - Алексей Стахнов - Программное обеспечение
- Разработка приложений в среде Linux. Второе издание - Майкл Джонсон - Программное обеспечение
- Искусство программирования для Unix - Эрик Реймонд - Программное обеспечение
- Fedora 8 Руководство пользователя - Денис Колисниченко - Программное обеспечение
- Linux Mint и его Cinnamon. Очерки применителя - Алексей Федорчук - Программное обеспечение
- Недокументированные и малоизвестные возможности Windows XP - Роман Клименко - Программное обеспечение
- ELASTIX – общайтесь свободно - Владислав Юров - Программное обеспечение
- Изучаем Windows Vista. Начали! - Дмитрий Донцов - Программное обеспечение
- Windows Vista - Сергей Вавилов - Программное обеспечение
- Операционная система UNIX - Андрей Робачевский - Программное обеспечение