Вы можете читать это руководство на досуге, чтобы узнать все о 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;
}