Вы можете читать это руководство на досуге, чтобы узнать все о DDD. Однако, чтобы приступить к использованию отладчика, достаточно знать лишь несколько возможностей. Они описаны в данной главе.
Программа-пример `sample.c' (см. раздел 1.1 Пример программы) обнаруживает
следующую ошибку. Программа sample
должна сортировать и
печатать свои аргументы в виде чисел, как в этом примере:
$ ./sample 8 7 5 4 1 3 1 3 4 5 7 8
Однако, при некоторых значениях аргументов она ошибается:
$ ./sample 8000 7000 5000 1000 4000 1000 1913 4000 5000 7000
Хотя вывод отсортирован и содержит верное число аргументов, некоторые
аргументы пропущены и заменены на странные числа; в данном случае
пропущено 8000
, а вместо него стоит 1913
.(3)
Давайте применим DDD, чтобы увидеть, что происходит. Сначала вы
должны скомпилировать `sample.c' для отладки (см. раздел 4.1 Компиляция для отладки), задав при компиляции флаг -g
:
$ gcc -g -o sample sample.c
Теперь вы можете вызвать DDD (см. раздел 2.1 Вызов DDD) для исполняемого файла
sample
:
$ ddd sample
Через несколько секунд появляется DDD. Окно с исходным текстом содержит код отлаживаемой программы; для прокрутки по файлу используйте полоску прокрутки.
Консоль отладчика (в самом низу) содержит информацию о версии DDD, а также подсказку GDB.(4)
GNU DDD Version 3.2.1, by Dorothea L@"utkehaus and Andreas Zeller. Copyright (C) 1999 Technische Universit@"at Braunschweig, Germany. Copyright (C) 1999 Universit@"at Passau, Germany. Reading symbols from sample...done. (gdb)
Первое, что нужно сейчас сделать -- установить точку останова
(см. раздел 5.1 Точки останова), что заставит sample
остановиться в
интересующем вас месте. Щелкните на пустом месте слева от инициализации
a
. Поле аргумента `():' теперь содержит позицию
(`sample.c:31'). Теперь щелкните на `Break', чтобы создать
точку останова в позиции `()'. Вы увидите, что на строке 31
появился маленький знак "стоп".
Следующее, что нужно сделать, -- действительно запустить программу, чтобы вы могли исследовать ее поведение (см. раздел 6. Запуск программы). Для запуска программы выберите `Program => Run'; появится диалоговое окно `Run Program'.
Теперь вы можете ввести в поле `Run with Arguments' аргументы
программы sample
. Введите здесь аргументы, приводящие к
ошибочному поведению -- то есть, `8000 7000 5000 1000 4000'.
Щелкните на `Run', чтобы началось выполнение с заданными вами
аргументами.
Теперь GDB запускает sample
. Через несколько мгновений,
когда достигнута точка останова, выполнение останавливается. Об этом
сообщается в консоли отладчика.
(gdb) break sample.c:31 Breakpoint 1 at 0x8048666: file sample.c, line 31. (gdb) run 8000 7000 5000 1000 4000 Starting program: sample 8000 7000 5000 1000 4000 Breakpoint 1, main (argc=6, argv=0xbffff918) at sample.c:31 (gdb)
Выполняемая в текущий момент строка обозначена зеленой стрелкой.
=> a = (int *)malloc((argc - 1) * sizeof(int));
Сейчас вы можете проверить значения переменных. Чтобы проверить простую
переменную, вы можете просто поместить указатель мыши над ее именем и
задержать его там. Спустя секунду всплывет маленькое окно со значением
этой переменной (см. раздел 7.1 Просмотр простых значений с помощью подсказок). Попробуйте проделать
это с `argv', чтобы увидеть ее значение (6
). Локальная
переменная `a' пока не проинициализирована; вы, вероятно, увидите
0x0
или какое-то другое значение неверного указателя.
Чтобы выполнить текущую строку, щелкните на кнопке `Next' из командной панели. Стрелка продвинется на следующую строку. Теперь снова укажите на `a' и увидите, что значение изменилось, и переменная `a' действительно стала инициализированной.
Чтобы исследовать отдельные значения массива `a', введите в поле аргумента `a[0]' (вы можете заранее очистить его, щелкнув на `():'), а затем щелкните на кнопку `Print'. Это напечатает текущее значение `()' в консоли отладчика (см. раздел 7.2 Печать простых значений в консоли отладчика). В нашем случае вы получите
(gdb) print a[0] $1 = 0 (gdb)
или какое-то другое значение (заметьте, что `a' была только размещена, но ее содержимое еще не проинициализировано.)
Чтобы увидеть все члены `a' одновременно, вы должны применить
особый оператор GDB. Поскольку `a' была размещена динамически,
GDB не знает ее размера; вы должны явно указать его, используя
оператор `@' (см. раздел 7.3.2.1 Фрагменты массива). Введите в поле
аргумента `a[0]@(argc - 1)' и щелкните кнопку `Print'. Вы
получите первые argc - 1
элементов `a', или
(gdb) print a[0]@(argc - 1) $2 = {0, 0, 0, 0, 0} (gdb)
Вместо того чтобы использовать `Print' для просмотра текущего значения `a' на каждом останове, вы можете также отобразить `a', то есть сделать так, чтобы ее значение показывалось автоматически. Щелкните на `Display' оставив в поле аргумента `a[0]@(argc - 1)'. Содержимое `a' теперь показывается в другом окне, окне данных. Для горизонтального поворота массива щелкните на `Rotate'.
Далее идет присваивание значений членам `a':
=> for (i = 0; i < argc - 1; i++) a[i] = atoi(argv[i + 1]);
Теперь вы можете щелкать на `Next' и снова на `Next', чтобы увидеть, как происходит присваивание отдельным членам `a'. Измененные члены подсвечиваются.
Для продолжения выполнения цикла используйте кнопку `Until'. Она велит GDB выполнять программу до тех пор, пока не будет достигнута строка, большая текущей. Щелкайте на `Until', пока не окажетесь на вызове `shell_sort':
=> shell_sort(a, argc);
В этом месте содержимое `a' должно быть равно `8000 7000 5000 1000 4000'. Снова щелкните на `Next', чтобы пройти через вызов `shell_sort'. DDD остановится на цикле
=> for (i = 0; i < argc - 1; i++) printf("%d ", a[i]);
и вы увидите, что после окончания `shell_sort' содержимое `a' стало равным `1000, 1913, 4000, 5000, 7000' -- то есть `shell_sort' каким-то образом испортил его.
Чтобы выяснить, что же случилось, выполните программу снова. На этот раз не проходите инициализацию, а перескочите прямо на вызов `shell_sort'. Удалите старую точку останова, выбрав ее и щелкнув на `Clear'. Затем создайте новую точку останова в строке 35, перед вызовом `shell_sort'. Для повторного выполнения программы выберите `Program => Run Again'.
Опять же, DDD остановится перед вызовом `shell_sort':
=> shell_sort(a, argc);
На этот раз вы хотите ближе исследовать, что делает `shell_sort'. Щелкните на `Step', чтобы войти в вызов `shell_sort'. Это оставит вашу программу на первой исполняемой строке,
=> int h = 1;
тогда как консоль отладчика говорит нам, что произошел вход в функцию:
(gdb) step shell_sort (a=0x8049878, size=6) at sample.c:9 (gdb)
Такой вывод, показывающий функцию (и ее аргументы), где остановлено выполнение `sample', называется отображением стека фреймов. Он дает представление о содержимом стека. Вы можете использовать `Status => Backtrace', чтобы узнать, в каком месте стека вы находитесь; если выбрать строку (или щелкнуть на `Up' или `Down'), вы сможете перемещаться по стеку. Обратите внимание на то, что отображение `a' исчезает, когда вы покидаете его фрейм.
Давайте теперь проверим правильность аргументов `shell_sort'. Вернувшись в самый нижний фрейм, введите в поле аргумента `a[0]@size' и щелкните на `Print':
(gdb) print a[0] @ size $4 = {8000, 7000, 5000, 1000, 4000, 1913} (gdb)
Сюрприз! Откуда взялось это лишнее значение 1913
? Ответ прост:
размер массива, передаваемый в функцию `shell_sort' как
`size', больше чем нужно на единицу -- странное число
1913
оказалось в памяти после `a'. И это значение также
сортируется.
Чтобы понять, действительно ли это является причиной ошибки, вы можете теперь присвоить `size' правильное значение (см. раздел 7.3.3 Присваивание переменных). Выберите в исходном коде `size' и щелкните на `Set'. Появится диалоговое окно, где вы можете отредактировать значение этой переменной.
Измените значение `size' на 5
и щелкните на `OK'.
Затем щелкните на `Finish', чтобы продолжить выполнение функции
`shell_sort':
(gdb) set variable size = 5 (gdb) finish Run till exit from #0 shell_sort (a=0x8049878, size=5) at sample.c:9 0x80486ed in main (argc=6, argv=0xbffff918) at sample.c:35 (gdb)
Получилось! Для `a' теперь показаны корректные значения `1000, 4000, 5000, 7000, 8000'.
Вы можете убедиться, что эти значения будут на самом деле напечатаны на стандартный вывод, выполнив программу дальше. Щелкните на `Cont', чтобы продолжить выполнение.
(gdb) cont 1000 4000 5000 7000 8000 Program exited normally. (gdb)
Сообщение `Program exited normally.' исходит от GDB; оно
говорит, что программа sample
завершила выполнение.
Найдя причину ошибки, вы теперь можете исправить исходный код. Щелкните на `Edit', чтобы отредактировать `sample.c', и измените строку
shell_sort(a, argc);
на корректный вызов
shell_sort(a, argc - 1);
Теперь вы можете перекомпилировать sample
$ gcc -g -o sample sample.c
и проверить (через `Program => Run Again'), что
sample
работает хорошо.
(gdb) run `sample' has changed; re-reading symbols. Reading in symbols...done. Starting program: sample 8000 7000 5000 1000 4000 1000 4000 5000 7000 8000 Program exited normally. (gdb)
Все готово; сейчас программа работает правильно. Вы можете завершить этот сеанс DDD с помощью `Program => Exit' или Ctrl+Q.
Это исходный файл `sample.c' программы-примера.
/* sample.c -- Sample C program to be debugged with DDD */ #include <stdio.h> #include <stdlib.h> static void shell_sort(int a[], int size) { int i, j; int h = 1; do { h = h * 3 + 1; } while (h <= size); do { h /= 3; for (i = h; i < size; i++) { int v = a[i]; for (j = i; j >= h && a[j - h] > v; j -= h) a[j] = a[j - h]; if (i != j) a[j] = v; } } while (h != 1); } int main(int argc, char *argv[]) { int *a; int i; a = (int *)malloc((argc - 1) * sizeof(int)); for (i = 0; i < argc - 1; i++) a[i] = atoi(argv[i + 1]); shell_sort(a, argc); for (i = 0; i < argc - 1; i++) printf("%d ", a[i]); printf("\n"); free(a); return 0; }