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

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

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


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


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

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

Дополнительные сведения о gdb

Отладчик проекта GNU – исключительно мощный инструмент, способный снабжать множеством сведений о внутреннем состоянии выполняющихся программ. В системах, поддерживающих средство аппаратно устанавливаемых контрольных точек, можно применять gdb для наблюдения за изменениями переменных в режиме реального времени. Аппаратно устанавливаемые контрольные точки – это функция некоторых ЦПУ; такие процессоры способны автоматически останавливаться при возникновении определенных условий, обычно доступе к памяти в заданной области. Кроме того, gdb может следить (watch) за выражениями. Это означает, что с потерей производительности gdb может остановить программу, когда выражение принимает конкретное значение, независимо от того, в каком месте программы выполнялось вычисление.

Точки останова можно устанавливать со счетчиками и условиями, так что они включаются только после фиксированного числа проходов или при выполнении условия.

Отладчик gdb также способен подключаться к уже выполняющимся программам. Это очень полезно при отладке клиент-серверных систем, поскольку вы сможете отлаживать некорректно ведущий себя серверный процесс во время выполнения без необходимости останавливать и перезапускать его. Можно компилировать программы, например, с помощью строки gcc -O -g, чтобы получить преимущества от применения оптимизации и отладочной информации. Недостаток заключается в том, что оптимизация может слегка переупорядочить текст программы, поэтому, когда вы будете выполнять программу в пошаговом режиме, может оказаться, что вы «скачете вперед и назад» по строкам, чтобы добиться того эффекта, что и в первоначальном тексте программы.

Отладчик gdb можно также применять для отладки аварийно завершившихся программ. Системы Linux и UNIX при аварийном завершении программы часто создают дамп ядра в файле с именем core. Это отображение карты памяти программы, которое содержит значения глобальных переменных в момент возникновения сбоя. Вы сможете использовать gdb для того, чтобы определить место в программе, вызвавшее аварийное завершение. Дополнительную информацию см. в интерактивном справочном руководстве к gdb.

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

Дополнительные средства отладки

Помимо полнофункциональных отладчиков, таких как gdb, Linux-системы обычно предоставляют и другие средства, которые можно применять для поддержки процесса отладки. Некоторые из них снабжают статической информацией о программе, другие обеспечивают динамический анализ.

Статический анализ предоставляет сведения только об исходном тексте программы. Программы ctags, cxref и cflow работают с исходными файлами и предлагают полезные данные о вызовах функций и их месте в программе.

Динамический анализ предоставляет информацию о том, как программа ведёт себя во время выполнения. Программы prof и gprof предлагают сведения о том, какие функции были выполнены, и сколько времени заняло их выполнение,

Давайте рассмотрим некоторые из этих средств и их вывод. Не все они будут доступны во всех системах, хотя у многих из этих средств есть свободно распространяемые версии.

Lint удаление ошибок из ваших программ

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

Более современные компиляторы C могут ценой производительности времени компиляции формировать аналогичные предупреждения. Утилиту lint, как таковую, обогнала стандартизация языка С. Поскольку средство основывалось на раннем компиляторе С, оно совсем не справляется с синтаксисом ANSI. Есть несколько коммерческих версий lint для UNIX и одна версия в Интернете для Linux, названная splint. Она известна под именем LClint, как часть проекта MIT (Massachusetts Institute of Technology, Массачусетский технологический институт), занимающегося разработкой средств формального описания. splint, средство подобное lint, может предоставлять полезные обзорные комментарии к программному коду. Найти splint можно по адресу http://www.splint.org.

Далее приведена первоначальная версия (debug0.c) программы-примера, которую вы уже отладили.

/*  1 */ typedef struct {

/*  2 */  char *data;

/*  3 */  int key;

/*  4 */ } item;

/*  5 */

/*  6 */ item array[j] = {

/*  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;

/* 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 */ }

В этой версии есть проблема в строке 20, где вместо предполагаемого оператора && применяется оператор &. Далее приведен отредактированный пример вывода splint, выполненной с этой версией программы. Обратите внимание на то, как она обнаруживает проблемы в строке 20 – тот факт, что вы не инициализировали переменную s и что возможны проблемы с условием из-за некорректного оператора.

neil@susel03:~/BLP4e/chapter10> splint -strict debug0.c

Splint 3.1.1 – 19 Mar 2005

debug0.c:7:18: Read-only string literal storage used as initial value for

               unqualified storage: array[0].data = «bill»

A read-only string literal is assigned to a non-observer reference. (Use -readonlytrans to inhibit warning)

debug0.c:8:18: Read-only string literal storage used as initial value for

               unqualified storage: array[1].data = «neil»

debug0.c:9:18: Read-only string literal storage used as initial value for

               unqualified storage: array[2].data = «john»

debug0.с:10:18: Read-only string literal storage used as initial value for

               unqualified storage: array[3].data = «rick»

debug0.c:11:18: Read-only string literal storage used as initial value for

               unqualified storage: array[4].data = «alex»

debug0.с:14:22: Old style function declaration

 Function definition is in old style syntax. Standard prototype syntax is

 preferred. (Use -oldstyle to inhibit warning)

debug0.с: (in function sort)

debug0.c:20:31: Variable s used before definition

 An rvalue is used that may not be initialized to a value on some execution

 path. (Use -usedef to inhibit warning)

debug0.с:20:23: Left operand of & is not unsigned value (boolean):

               i < n & s != 0

 An operand to a bitwise operator is not an unsigned values. This may have

 unexpected results depending on the signed representations. (Use

 -bitwisesigned to inhibit warning).

debug0.c:20:23: Test expression for for not boolean, type unsigned int:

               i < n & s != 0

 Test expression type is not boolean or int. (Use -predboolint to inhibit

 warning);

debug0.с:25:41: Undocumented modification of a[]: a[j] = a[j + 1]

 An externally-visible object is modified by a function with no /*@modifies@*/

 comment. The /*@modifies ... @*/ control comment can be used to give a

 modifies list for an unspecified function. (Use -modnomods to inhibit

 warning)

debug0.c:26:41: Undocumented modification of a[]: a[j + 1] = t

debug0.c:20:23: Operands of & are non-integer (boolean) (in post loop test):

               i < n & s != 0

 A primitive operation does not type check strictly. (Use -strictops to

 inhibit warning)

debug0.с:32:14: Path with no return in function declared to return int

 There is a path through a function declared to return a value on which there

 is no return statement. This means the execution may fall through without

 returning a meaningful result to the caller. (Use -noret to inhibit

 warning)

debug0.с:34:13: Function main declared without parameter list

 A function declaration does not have a parameter list. (Use -noparams

 to inhibit warning)

debug0.с: (in function main)

debug0.с:36:22: Undocumented use of global array

 A checked global variable is used in the function, but not listed in its

 globals clause. By default, only globals specified in .lcl files are

 checked.

 To check all globals, use +allglobals. To check globals selectively use

 /*@checked@*/ in the global declaration. (Use -globs to inhibit warning)

debug0.с:36:17: Undetected modification possible from call to unconstrained

               function sort: sort

 An unconstrained function is called in a function body where

 modifications are checked. Since the unconstrained function may modify

 anything, there may be undetected modifications in the checked function.

 (Use -modunconnomods to inhibit warning)

debug0.c:36:17: Return value (type int) ignored: sort(array, 5)

 Result returned by function call is not used. If this is intended, can

 cast result to (void) to eliminate message. (Use -retvalint to inhibit

 warning)

debug0.c:37:14: Path with no return in function declared to return int

debug0.c:6:18: Variable exported but not used outside debug0: array

 A declaration is exported, but not used outside this module. Declaration

 can use static qualifier. (Use -exportlocal to inhibit warning)

debug0.c:14:13: Function exported but not used outside debug0: sort

 debug0.c:15:17: Definition of sort

debug0.c:6:18: Variable array exported but not declared in header file

 A variable declaration is exported, but does not appear in a header

 file. (Used with exportheader.) (Use -exportheadervar to inhibit warning)

debug0.c:14:13: Function sort exported but not declared in header file

 A declaration is exported, but does not appear in a header file. (Use

 -exportheader to inhibit warning)

debug0.c:15:17: Definition of sort

Finished checking – 22 code warnings

$

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

Она также обнаружила две реальные ошибки в следующем фрагменте кода:

/* 18 */  int s;

/* 19 */

/* 20 */  for(; i < n & s != 0; i++) {

/* 21 */   s = 0;

Средство splint определило (выделенные цветом строки предыдущего вывода), что переменная s используется в строке 20, но не была при этом инициализирована, и что оператор & стоит на месте более обычного оператора &&. В данном случае старшинство оператора изменяет значение условия и создает проблему в программе.

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

Средства, отслеживающие вызовы функций

Три утилиты – ctags, cxref и cflow – формируют часть стандарта X/Open и, следовательно, должны включаться в системы, представляемые как системы UNIX с программными средствами разработки.

Примечание

Эти утилиты и другие, упоминаемые в этой главе, могут не входить в состав вашего дистрибутива Linux. Если они пропущены, можно поискать их реализации в Интернете. Хорошая отправная точка (для дистрибутивов Linux, поддерживающих формат RPM-пакетов) – Web-сайты http://rpmfind.net и http://rpm.pbone.net. Можно попытаться поискать в нескольких репозитариях для конкретных дистрибутивов, включая http://ftp.gwdg.de/pub/opensuse/ для openSUSE, http://rpm.livna.org для Fedora и http://packages.slackware.it/ для Slackware.

ctags

Программа ctags создает алфавитный указатель функций. Для каждой функции вы получаете перечень мест в программе, где она применяется, как алфавитный указатель к книге.

ctags [-a] [-f filename] sourcefile sourcefile ...

ctags -x sourcefile sourcefile ...

По умолчанию ctags создает в текущем каталоге файл с именем tags, содержащий для каждой функции, объявленной в любом из входных файлов исходного кода, строки следующего вида:

announce app_ui.c /^static void announce(void) /

Каждая строка файла содержит имя функции, файл, в котором она объявлена, и регулярное выражение, которое можно использовать для поиска описания функции в файле. Некоторые редакторы, например Emacs, могут применять файлы этого вида для навигации в исходном тексте программы.

Кроме того, с помощью опции в программе ctags (если она доступна в вашей версии программы) вы можете формировать строки аналогичного вида в стандартном файле вывода.

find_cat 403 appui.с static cdc_entry find_cat(

Можно перенаправить вывод в другой файл с помощью опции -f filename и добавить его в конец существующего файла, указав опцию .

cxref

Программа cxref анализирует исходный текст на языке С и формирует перекрестные ссылки. Она показывает, где в программе упоминается каждое символическое имя (переменная, директива #define и функция). Программа создает отсортированный список с указанием места определения каждого идентификатора, которое помечается звездочкой, как показано далее:

 SYMBOL                FILE  FUNCTION LINE

 BASENID               prog.с      –  *12 *96 124 126 146 156 166

 BINSIZE               prog.с      –  *30 197 198 199. 206

  BUFMAX               prog.с      –  *44 45 90

  BUFSIZ /usr/include/stdio.h      –  *4

     EOF /usr/include/stdio.h      –  *27

    argc               prog.с      –  36

                       prog.с    main  *37 61 81

    argv               prog.с      –  36

                       prog.с    main  *38 61

calldata               prog.с      –  *5

                       prog.с    main  64 188

  calls                prog.с      –  *19

                       prog.с     main 54

На машине одного из авторов этой книги предыдущий вывод был сгенерирован в каталоге с исходными файлами приложения с помощью команды

$ cxref *.с *.h

но точный синтаксис зависит от версии. См. документацию к вашей системе и интерактивное справочное руководство для получения дополнительной информации о том, включена ли программа cxref и как ее применять.

cflow

Программа cflow выводит дерево вызовов функций – схему, показывающую, какие функции вызывают другие функции, какие функции вызываются этими другими и т.д. Эта схема полезна для выяснения структуры программы, понимания ее принципов действия и наблюдения за влиянием изменений, внесенных в функцию. Некоторые версии программы cflow могут работать с объектными файлами так же, как с исходными. Подробности см. в интерактивном справочном руководстве.

Далее приведен пример вывода, полученный версией cflow (cflow-2.0), которая есть в Интернете и поддерживается Марти Лейснером (Marty Leisner).

0  file_ungetc {prcc.c 997}

1  main {prcc.c 70}

2      getopt {}

3      show_all_lists {prcc.c 1070}

4          display_list {prcc.c 1056}

5              printf {}

6          exit {}

7      exit {}

9      usage {prcc.c 59}

10         fprintf {}

11         exit {}

Пример информирует о том, что функция main вызывает (среди прочих) функцию show_all_lists и что show_all_lists в свою очередь вызывает функцию display_list, которая вызывает функцию printf.

У этой версии cflow есть опция -i, которая формирует инвертированный потоковый граф. Утилита cflow перечисляет для каждой функции другие функции, вызывающие данную. Звучит не очень понятно, но на самом деле все просто. Далее приведен пример:

19  display_list {prcc.c 1056}

20      show_all_lists {prcc.c 1070}

21  exit {}

22      main {prcc.c 70}

23      show_all_lists {prcc.c 1070}

24      usage {prcc.c 59}

25  ...

74  printf {}

75      display_list {prcc.c 1056}

76      maketag {prcc.c 4 87}

77  show_all_lists {prcc.c 1070}

78      main {prcc.c 70}

79  ...

99  usage {prcc.c 59}

100     main {prcc.c 70}

В примере показано, что функцию exit, например, вызывают функции main, show_all_lists и usage.

Выполнение профилирования с помощью prof/gprof

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

Программа prof (и ее эквивалент в проекте GNU, gprof) выводит отчёт из файла трассировки выполнения, который формируется во время выполнения профилируемой программы. Профилируемый исполняемый файл создается с помощью флага компилятора -p (для prof) или флага -pg (для gprof).

$ cc -pg -о program program.с

Программа компонуется со специальной библиотекой С, и в нее включается контрольный код. В конкретных системах он может отличаться, но общая цель – такая организация программы, которая позволяет часто прерывать выполнение и записывать этап выполнения. Контрольные данные записываются в файл mon.out (gmon.out для gprof) в текущем каталоге.

$ ./program

$ ls -ls

2 -rw-r–r– 1 neil users 1294 Feb 4 11:48 gmon.out

Программа prof/gprof читает эти контрольные данные и выводит отчет. См. подробности, касающиеся опций программы, в интерактивном справочном руководстве. Далее в качестве примера приведен вывод (сокращенный) программы gprof.

cumulative  self    self   total

   time    seconds seconds  calls ms/call ms/call            name

   18.5       0.10    0.10   8664    0.01    0.03      doscan [4]

   18.5       0.20    0.10                            mcount (60)

   14.8       0.28    0.08  43320    0.00    0.00     _number [5]

    9.3       0.33    0.05   8664    0.01    0.01 _format_arg [6]

    7.4       0.37    0.04 112632    0.00    0.00     _ungetc [8]

    7.4       0.41    0.04   8757    0.00    0.00    _memccpy [9]

    7.4       0.45    0.04      1   40.00  390.02       _main [2]

    3.7       0.47    0.02     53    0.38    0.38      _read [12]

    3.7       0.49    0.02                             w4str [10]

    1.9       0.50    0.01  26034    0.00    0.00    _strlen [16]

    1.9       0.51    0.01   8664    0.00    0.00    strncmp [17]

Проверки соблюдения условий

Несмотря на то, что вставка на этапе разработки программы с помощью условной компиляции отладочного кода, такого как вызовы printf, распространена, иногда оставлять такие сообщения в поставляемой программе непрактично. Но часто проблемы возникают во время работы программы из-за некорректных допущений или исходных данных, а не из-за ошибок кодирования. Это события, которых «не может быть никогда». Например, функция может быть написана в расчете на то, что ее входные параметры будут в определенном диапазоне. Если передать ей некорректные данные, она может сделать некорректной работу всей системы.

В тех случаях, когда внутренняя логика системы нуждается в подкреплении, X/Open предоставляет макрос assert, применяемый для проверки правильности исходных данных и остановки выполнения программы в противном случае.

#include

void assert(int expression)

Макрос assert вычисляет выражение и, если оно не равно нулю, выводит некоторую диагностическую информацию о стандартной ошибке и вызывает функцию abort для завершения программы.

Заголовочный файл assert.h определяет макросы в зависимости от определения флага NDEBUG. Если NDEBUG определен во время обработки заголовочного файла, assert определяется по существу как ничто. Это означает, что вы можете отключить проверки заданных выражений во время компиляции, компилируя с опцией -DNDEBUG или вставив перед включением файла assert.h строку

#define NDEBUG

в каждый исходный файл.

Этот метод применения порождает проблему. Если вы используете assert во время тестирования, но отключите макрос в рабочем коде, в вашем рабочем коде может оказаться менее строгая проверка, чем применявшаяся в процессе его тестирования. Обычно макросы assert не оставляют включенными в рабочем коде – вряд ли вам понравится рабочий код, предоставляющий пользователю недружелюбное сообщение assert failed и останавливающий программу. Быть может, лучше написать свою отслеживающую ошибки подпрограмму, которая проверяет выражение, использовавшееся в макросе, но не нуждается в полном отключении в рабочем коде.

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

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

Упражнение 10.2. Программа assert.c.

Далее приведена программа assert.c, определяющая функцию, которая должна принимать положительное значение. Она защищает от ввода некорректного аргумента благодаря применению макроса assert.

После включения заголовочного файла assert.h и функции "квадратный корень", проверяющей положительное значение параметра, вы можете писать функцию main.

#include

#include

#include

#include

double my_sqrt(double x) {

 assert(x >= 0.0);

 return sqrt(x);

}

int main() {

 printf(«sqrt +2 = %gn», my_sqrt(2.0));

 printf(«sqrt -2 = %gn», my_sqrt(-2.0));

 exit(0);

}

Теперь при выполнении программы вы увидите нарушение в макросе assert при передаче некорректного значения. Точный формат сообщения о нарушении условия макроса assert в разных системах разный.

$ сс -о assert assert.с -lm

$ ./assert

sqrt +2 = 1.41421

assert: assert.c:7: my_sqrt: Assertion 'x >= 0.0' failed.

Aborted

$

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

Когда вы попытаетесь вызвать функцию my_sqrt с отрицательным числом, макрос assert даст сбой. Он предоставляет файл и номер строки, в которой нарушено условие и само нарушенное условие. Программа завершается прерыванием abort. Это результат вызова abort макросом assert.

Если вы перекомпилируете программу с опцией -DNDEBUG, макрос assert не компилируется, и вы получаете NaN (Not a Number, не число) – значение, указывающее на неверный результат при вызове функции sqrt из функции my_sqrt.

$ cc -о assert -DNDEBUG assert.с -lm

$ ./assert

sqrt +2 = 1.41421

sqrt -2 = nan

$

Некоторые более старые версии математической библиотеки генерируют исключение для математической ошибки, и ваша программа будет остановлена с сообщением "Floating point exception" ("Исключение для числа с плавающей точкой") вместо возврата NaN.


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

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