355 500 произведений, 25 200 авторов.

Электронная библиотека книг » Нейл Мэтью » Основы программирования в Linux » Текст книги (страница 38)
Основы программирования в Linux
  • Текст добавлен: 21 сентября 2016, 17:59

Текст книги "Основы программирования в Linux"


Автор книги: Нейл Мэтью


Соавторы: Ричард Стоунс
сообщить о нарушении

Текущая страница: 38 (всего у книги 67 страниц)

KDevelop

KDevelop – это IDE для программ на языках С и С++. Она обеспечивает особую поддержку при создании приложений, выполняющихся в среде K Desktop Environment (KDE), одном из двух основных современных пользовательских графических интерфейсов в системах Linux. Ее можно использовать и для проектов других типов, включая простые программы на языке С.

KDevelop – бесплатное программное обеспечение, выпускаемое в соответствии с требованиями Общедоступной лицензии проекта GNU (General Public License, GPL), и имеющееся во многих дистрибутивах Linux. Самую свежую версию можно загрузить с Web-сайта http://www.kdevelop.org. Проекты, созданные с помощью среды KDevelop, по умолчанию следуют стандартам, принятым для проектов GNU. Например, они будут применять утилиту autoconf для генерации make-файлов, которые специально приспособлены к среде, для которой формируются. Это означает, что проект готов к распространению в виде исходного кода, который с большой вероятностью будет успешно откомпилирован в других системах.

Проекты KDevelop также содержат шаблоны для создания документации, текст лицензии GPL и общие инструкции по установке. Количество файлов, генерируемых при создании проекта KDevelop, может испугать, но познакомьтесь с кем-нибудь, кто загружал из Интернета и компилировал типовое приложение GPL.

Рис. 9.2

В среде KDevelop существует поддержка систем CVS и Subversion для управления исходным программным кодом, и приложения могут редактироваться и отлаживаться без выхода из среды разработки. На рис. 9.2 и 9.3 показано стандартное приложение на С в среде KDevelop (еще одна программа, приветствующая мир), которое редактируется и выполняется.

Рис. 9.3

Другие среды разработки

Для ОС Linux имеется в наличии иди разрабатывается множество других редакторов и IDE, как бесплатных, так и коммерческих. Несколько самых интересных приведено в табл. 9.6.

Таблица 9.6


EclipseПлатформа на базе языка Java и IDE http://www.eclipse.org
AnjutaIDE для пользовательского графического интерфейса GNOME http://anjuta.sourceforge.net/
QtEZIDE для пользовательского графического интерфейса KDE http://projects.uid0.sk/qtez/
SlickEditКоммерческий редактор кода с поддержкой многих языков http://www.slickedit.com/

Резюме

В этой главе вы увидели лишь несколько средств ОС Linux, делающих разработку и распространение программ управляемыми. Первое и, может быть, самое важное – вы применили команду make и make-файлы для управления множественными исходными файлами. Далее вы познакомились с управлением исходным программным кодом с помощью систем RCS и CVS, которые позволяют отслеживать изменения в процессе разработки программ. Затем вы рассмотрели распространение программ с помощью команды patch, совместного применения команд tar и gzip и RPM-пакетов. В заключение вы бросили взгляд на одно из средств, IDE KDevelop, немного облегчающее цикл разработки программы, включающий редактирование, выполнение и отладку.

Глава 10
Отладка

По утверждению Software Engineering Institute (Институт программных разработок) и IEEE (Institute of Electrical and Electronics Engineers, Институт инженеров по электротехнике и электронике) в любом значимом фрагменте программного обеспечения первоначально всегда есть дефекты, примерно два на 100 строк программного кода. Эти ошибки приводят к тому, что программы и библиотеки не работают так, как требуется, часто заставляя программу вести себя иначе, чем предполагалось. Отслеживание ошибок, их идентификация и удаление могут потребовать от программиста больших затрат времени на этапе разработки.

В этой главе мы рассмотрим недочеты программного обеспечения и некоторые средства и методы исследования характерных примеров ошибочного поведения. Это не то же самое, что тестирование (задача проверки работы программы во всех возможных условиях или обстоятельствах), хотя тестирование и отладка конечно же взаимосвязаны, и многие ошибки обнаруживаются в процессе тестирования.

Будут обсуждаться следующие темы:

□ типы ошибок;

□ общие методы отладки;

□ отладка с помощью gdb и других средств;

□ проверка соблюдения условий (макрос assert);

□ устранение ошибок использования памяти.

Типы ошибок

Ошибка, как правило, возникает по одной из нескольких причин, каждая из которых предполагает конкретный метод выявления и устранения.

□ Ошибки описания или спецификации. Если программа неверно определена, она, несомненно, не сможет выполняться, как требуется. Даже лучший программист в мире может порой написать неверную программу. Прежде чем приступить к программированию (или разработке), убедитесь в том, что вы точно знаете и четко представляете, что должна делать программа. Вы обнаружите и устраните множество ошибок спецификации (если не все), обсуждая требования и получая подтверждение их правильности у тех, кто будет применять вашу программу в дальнейшем.

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

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

Примечание

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

 Попытайтесь выполнить основную часть программы на бумаге, этот процесс называют формальным прогоном. Для наиболее важных подпрограмм запишите значения на входе и вычислите шаг за шагом выходные значения. Для отладки совсем не обязательно всегда применять компьютер, иногда именно компьютер создает проблемы. Даже разработчики, пишущие библиотеки, компиляторы и операционные системы, делают ошибки! С другой стороны, не спешите винить во всем используемые программные средства; гораздо вероятнее, что ошибка закралась в вашу новую программу, а не в компилятор.

Общие методы отладки

Существует несколько разных подходов к отладке и тестированию типовой программы 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

/* 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. Как и компилятор, они анализируют код и сообщают о фрагментах кода, которые могут быть некорректными.

Оснащение средствами контроля

Оснащение средствами контроля – это вставка в программу кода для сбора дополнительной информации о поведении программы во время ее выполнения. Очень популярна вставка вызовов функции printf для вывода значений переменных на разных стадиях выполнения программы. Вы можете с пользой для себя добавить несколько вызовов printf, но должны знать о том, что этот процесс повлечет за собой дополнительные редактирование и компиляцию при любом изменении программы и, конечно, вам придется удалить код, когда ошибки будут исправлены.

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

#ifdef DEBUG

 printf(«variable x has value = %dn», x);

#endif

Вы можете компилировать программу с флагом компилятора -DDEBUG для определения символического имени DEBUG и включения дополнительного кода и без этого флага – для удаления отладочного кода. Можно создать и более сложный вариант использования пронумерованных отладочных макросов:

#define BASIC_DEBUG 1

#define EXTRA_DEBUG 2

#define SUPER_DEBUG 4

#if (DEBUG & EXTRA_DEBUG)

 printf...

#endif

В этом случае вы всегда должны определять макрос DEBUG, но можете настраивать объем отладочной информации или уровень детализации. Флаг компилятора -DDEBUG=5 в нашем примере активизирует макросы BASIC_DEBUG и SUPER_DEBUG, но не EXTRA_DEBUG. Флаг DDEBUG=0 отключит всю отладочную информацию. С другой стороны, вставка следующих строк устранит необходимость задания в командной строке DEBUG, если отладки не требуется.

#ifndef DEBUG

#define DEBUG 0

#endif

Несколько макросов, определенных препроцессором С, могут предоставить отладочную информацию. Эти макросы раскрываются для предоставления сведений о текущей компиляции (табл. 10.1).

Обратите внимание на то, что приведенные символические имена начинаются и заканчиваются двумя символами подчеркивания. Это стандартное правило для символических имен препроцессора, и вы должны аккуратно выбирать идентификаторы, чтобы избежать конфликтов. Термин "текущие" в предыдущих описаниях указывает на момент выполнения препроцессорной обработки, т.е. время и дата запуска компилятора и обработки файла.

Таблица 10.1


__LINE__ Десятичная константа, предоставляющая номер текущей строки
__FILE__ Строка, предоставляющая имя текущего файла
__DATE__ Строка в форме «ммм дд гггг», текущая дата
__TIME__ Строка в форме «чч:мм:сс», текущее время

Выполните упражнение 10.1.

Упражнение 10.1. Отладочная информация

Далее приведена программа cinfo.c, которая выводит дату и время компиляции, если включен режим отладки.

#include

# include

int main() {

#ifdef DEBUG

 printf("Compiled: " __DATE__ " at " __TIME__ «n»);

 printf(«This is line %d of file %sn», __LINE__, __FILE__);

#endif

 printf(«hello worldn»);

 exit(0);

}

Когда вы откомпилируете эту программу с включенным режимом отладки (используя флаг -DDEBUG), то увидите следующие сведения о компиляции:

$ cc -о cinfo -DDEBUG cinfo.c

$ ./cinfo

Compiled: Jun 30 2007 at 22:58:43

This is line 8 of file cinfo.c

hello world

$

Как это работает

Препроцессор С, часть компилятора, отслеживает текущую строку и текущий файл во время компиляции. Он подставляет текущие (времени компиляции) значения этих переменных везде, где обнаруживает символические имена __LINE__ и __FILE__. Дата и время компиляции становятся доступными аналогичным образом.

Поскольку __DATE__ и __TIME__ – строки, вы можете объединить их в функции printf с помощью строк формата, т.к. в языке С ANSI смежные строки воспринимаются как одна.

Отладка без перекомпиляции

Прежде чем двигаться дальше, стоит отметить, что существует способ применения функции printf, позволяющий отлаживать программу без применения метода #ifdef DEBUG, требующего перекомпиляции программы перед ее использованием.

Метод заключается во вставке глобальной переменной как флага отладки, разрешении опции -d в командной строке, которая дает возможность пользователю включить отладку даже после того, как программа была введена в эксплуатацию, и включении функции мониторинга процесса отладки. Теперь можно вкраплять в код программы строки, подобные следующим:

if (debug) {

 sprintf(msg, ...)

 write_debug(msg)

}

Записывать вывод отладки следует в стандартный поток ошибок stderr или, если это не годится из-за характера программы, используйте возможности мониторинга, предоставляемые функцией syslog.

Если вы вставляете в программу подобную трассировку для решения проблем, возникающих на этапе разработки, просто оставьте этот код в программе. Если вы будете чуть внимательнее, чем всегда, такой подход не вызовет никаких проблем. Выигрыш проявится, когда программа будет введена в эксплуатацию; если пользователи обнаружат проблему, они смогут выполнить программу в режиме отладки и диагностировать ошибки для вас. Вместо известия о том, что программа выдает сообщение о нарушении сегментации, они смогут написать, что конкретно делает программа в ходе выполнения, а не только описать свои действия. Разница может оказаться огромной.

У этого метода есть явный недостаток: программа становится больше, чем должна быть. В большинстве случаев это, скорее, мнимая проблема, чем реальная. Программа может стать на 20–30% больше, но чаще всего это не оказывает никакого существенного влияния на ее производительность. Снижение производительности наступает при увеличении размера на несколько порядков, а не на небольшую величину.


    Ваша оценка произведения:

Популярные книги за неделю