Шаг 8.
Средства отладки программ. Выполнение по шагам и трассировка

   Мы начинаем рассматривать основные средства, применяемые для отладки программы.


Выполнение по шагам и трассировка


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

   Отладочные средства среды программирования для DOS Borland Pascal 7.0 сосредоточены, в основном, в двух пунктах меню: Run и Debug.

    При компиляции программы компилятор всегда сохраняет список используемых идентификаторов, который называется таблицей идентификаторов. В этом списке отслеживаются имена всех переменных, констант, типов, процедур и функций. Для целей отладки там сохраняются также номера строк исходных файлов, где встречаются все эти идентификаторы. Выбрав в диалоговом окне Options|Compiler параметр Debug Information (Отладочная информация) или задав директиву компилятора {$D+}, вы указываете компилятору, что в таблицу идентификаторов нужно добавить информацию о номерах строк. Эта информация используется при отладке программ.

    В диалоговом окне параметров отладчика Debugger Options (Options|Debugger) вы можете сообщить компилятору, нужно ли генерировать отладочную информацию для использования встроенного или автономного отладчика (такого как Turbo Debugger), или для обоих. Если вы хотите использовать встроенный отладчик, то нужно выбрать параметр Integrated debugging/browsing (устанавливается по умолчанию).

    Если вы пишете большую программу, которая использует модули, и отладочная информация получается слишком объемной, то можно сократить объем этой информации для отдельных модулей, используя в них директиву компилятора {$L-} или отменив в диалоговом окне Compiler Options параметр Local Symbols (Информация о локальных идентификаторах).

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

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

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

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

Выполнение по шагам и трассировка.

    Выполнение по шагам - это простейший способ выполнения программы по элементарным фрагментам. Выбор команды Run|Step Over (клавиша F8) вызывает выполнение отладчиком всего кода в подсвеченной строке, включая любые вызываемые на ней процедуры или функции. После этого подсвеченная строка перемещается на следующую строку программы.

    Часто при выполнении программы по шагам требуется отслеживать значения переменных. Для этого можно открыть окно просмотра значений переменных, выполнив команду Debug|Watch. На экране появится окно Watches. Если это окно будет активным, то можно изменить его размеры и местоположение, а также пользоваться меню, пункты которого расположены в последней строке экрана дисплея.

    Для изменения размеров и местоположения окна используются клавиши Ctrl+F5. Граница окна становится зеленого цвета и можно пользоваться следующими клавишами:

    Теперь перечислим пункты меню этого окна:

Примечание. Для перехода в требуемое окно используется комбинация клавиш Alt+<номер окна>. Комбинация клавиш Alt+0 выводит на экран диалоговое окно со списком всех открытых окон.

    Окно Watches позволяет вам несколькими способами представлять значения просматриваемых выражений, добавляя запятую и один или более спецификаторов формата. Например, хотя целочисленные значения выводятся обычно в десятичном виде, указав после него H, вы можете задать вывод выражения в шестнадцатеричном формате. Допустимые спецификаторы формата и их действие перечисляются в таблице 1.

Таблица 1. Спецификаторы формата
Символ
Тип, на который он влияет
Функция
$, H или X
Целочисленные типы.
Шестнадцатеричный. Выводит целочисленные значения с префиксом $, включая те, которые содержатся в структуре данных.
C
Char, строковые типы.
Символьный. Выводит специальные символы для кодов ASCII 0..31. По умолчанию такие символы выводятся в виде значений #xx.
D
Целочисленные типы.
Десятичный. Выводят целочисленные значения в десятичном виде, включая те, которые содержатся в структурах данных.
Fn
С плавающей точкой.
С плавающей точкой. Выводит n значащих цифр, где n лежит в диапазоне 2..18 (по умолчанию - 11).
nM
Все.
Дамп памяти. Выводит n байт памяти, начиная с адреса, заданного выражением. Если n не задано, то по умолчанию оно равно значению размера в байтах типа переменной.
P
Указатели.
Указатель. Выводит указатели по адресу <сегмент>:<смещение>.
R
Записи, объекты.
Запись. Выводит имена полей, например: (X:1; Y:10; Z:5) вместо (1, 10, 5).
S
Char, строки.
Строки. Выводит символы ASCII 0..31 в виде #xx. Используется только для модификации дампов памяти.

    Проиллюстрируем механизм пошагового выполнения программы. Для этого рассмотрим следующий пример:

Program StepTest;
Function Negate(X: Integer): Integer;
  Begin
    Negate := -X; 
  End;
Var
  I: Integer; 
Begin
  For I := 1 To 10 Do Writeln(Negate(I)); 
End.
Текст этого примера можно взять здесь.

    В окно просмотра значений переменных поместите переменную I и нажмите клавишу F8. Подсвеченная строка перемещается на оператор Begin в начале основного цикла, поскольку это первое, что выполняется в программе. Значение переменной I становится равным 0, так как осуществляется неявное присваивание всем переменным начальных значений. Второе нажатие клавиши F8 выполняет Begin и перемещает подсвеченную строку вниз до оператора For, расположенного на следующей строке. После этого нажатие F8 вызывает выполнение всего цикла For; на экран пользователя выводятся числа от -1 до -10, а строка выполнения перемещается к End. Значение переменной I становится равным 10.

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


.   .   .   .
Begin
  For  I := 1 To 10 Do
     Writeln(Negate(I)); 
End.

    Такая программа будет в точности эквивалентна предыдущей версии, и генерируемый код будет идентичен. Но поскольку оператор Writeln теперь находится на отдельной строке, отладчик может интерпретировать его отдельно. Если теперь вы будете нажимать клавишу F8, то увидите, что подсвеченная строка будет при выполнении цикла 10 раз возвращаться на Writeln, а значение переменной I будет изменяться от 1 до 10.

    Трассировка программы во многом аналогична ее выполнению по шагам. Единственное исключение состоит в том, что когда встречается оператор вызова процедуры или функции, при трассировке эти процедуры и функции также выполняются по шагам, а при простом выполнении по шагам управление возвращается после завершения выполнения подпрограммы.

    Например, чтобы выполнить трассировку программы из предыдущего примера, выберите команду Run|Trace Into (клавиша F7). Когда вы в первый раз делаете это, управление перемещается на оператор Begin основной программы. Повторное нажатие F7 снова перемещает строку управления на оператор For. После этого нажатие клавиши F7 трассирует вызов функции Negate - подсвеченная строка перемещается на оператор Begin в блоке функции. Если вы продолжаете нажимать F7, подсвеченная строка перемещается по функции, а затем, когда вы дойдете до оператора End, возвращается к оператору вызова.

    Формат вашей программы влияет на поведение строки выполнения при трассировке, хотя и не в такой степени, как при пошаговом выполнении. Если код сформатирован как в первом примере, то трассировка оператора For приводит к выполнению 10 раз функции Negate. Если вы разобьете оператор For на две строки, то трассировка оператора End функции возвращает подсвеченную строку на ту строку основной программы, которая будет выполняться следующей. Первые девять раз это снова будет вызов функции. В десятый раз строка выполнения перемещается на оператор End программы.

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

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

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

    Если вы выполняете в программе компоновку с внешним кодом, используя для этого директиву компилятора {$L <имя файла>}, то если компонуемый OBJ-файл содержит отладочную информацию, вы можете трассировать этот код или выполнять его по шагам. Среда программирования "ничего не знает" об отлаживаемом коде в этих модулях, но она будет показывать соответствующие строки в исходном коде.

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


    На следующем шаге мы рассмотрим, как организовать выполнение программы до заданной точки.


Выполнение по шагам и трассировка


Предыдущий шаг Содержание Следующий шаг