Каждый программист знает, что программу надо не только написать, иначе говоря - "заставить работать",
а еще и отладить, т.е. заставить ее работать правильно...
Для того, чтобы делать это быстро и эффективно, желательно научиться пользоваться встроенными в IDE средствами
отладки программ...
Интегрированная интерактивная среда разработки программ Borland Pascal (IDE) включает в себя ряд средств,
облегчающих разработку программ: автоматическое управление проектами, средств обеспечения модульной структуры
программы, быструю компиляцию и простые в использовании оверлеи. Но, несмотря на все это, Ваша программа все
равно может содержать ошибки, что не позволит ей корректно работать.
IDE для DOS Borland Pascal предоставляет вам инструментальные средства для отладки программ, то есть поиска
и исправления ошибок. В этой статье описываются инструментальные средства и процедуры отладки программы в
интегрированной среде Борланд Паскаль (большинство описанных возможностей применимо также к IDE FreePascal-я).
Пошаговый прогон: какая разница между F4, F7 и F8?
Что такое шаг?
Когда Вы отлаживаете программу, наименьшим выполняемым элементом является строка. Это означает, что Вы можете управлять отладкой до уровня отдельной строки исходного кода программы. Поэтому, если на одной строке программы содержится несколько операторов Паскаля, эти операторы не могут быть отлажены индивидуально. С другой стороны, с целью отладки оператор можно разбить на несколько строк, каждая из которых будет выполняться за один шаг.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.Если в окне редактирования Вы выведете StepTest и нажмете клавишу F8, то строка выполнения перемещается на оператор begin в начале основного цикла, поскольку это первое, что выполняется в программе. Второе нажатие клавиши F8 выполняет begin и перемещает строку выполнения вниз до оператора for на следующей строке. После этого нажатие F8 вызывает выполнение всего цикла for; на экран пользователя выводятся числа от -1 до -10, а строка выполнения перемещается к end.
begin for I := 1 to 10 do WriteLn(Negate(I)); end.Поскольку оператор Паскаля может занимать несколько строк, такая программа будет в точности эквивалентна предыдущей версии, и генерируемый код будет идентичен. Но поскольку оператор WriteLn теперь находится на отдельной строке, отладчик может интерпретировать его отдельно. Если теперь Вы будете нажимать клавишу F8, то увидите, что строка выполнения будет при выполнении цикла 10 раз возвращаться на WriteLn. Трассировка программы во многом аналогична ее выполнению по шагам. Единственное исключение состоит в том, что когда встречается оператор вызова процедуры/функции, при трассировке эти процедуры и функции также выполняются по шагам, а при простом выполнении по шагам управление возвращается вам после завершения выполнения подпрограммы.
Трассировка или выполнение по шагам?
Пошаговое выполнение и трассировка выполняют одно и то же действие, кроме того случая, когда строка выполнения находится на строке вызова процедуры/функции, или когда выполняется оператор begin в начале программы или модуля, который использует другие модули.Пошаговое выполнение и трассировка методов объектов
Если в программе используются объекты, отладчик ведет себя аналогично своему поведению в случае обычных процедур/функций. Пошаговое выполнение метода интерпретирует метод как один шаг, возвращая управление к отладчику после того как метод завершает выполнение. Трассировка метода загружает и выводит на экран код метода и трассирует его операторы. Иногда, конечно, нежелательно выполнять по шагам всю программу только для того, чтобы добраться до того места, где возникает проблема. Отладчик дает Вам возможность выполнять сразу большой фрагмент программы до той точки, где вы хотите начать выполнение по шагам.
Внимание: С использованием этой команды связана одна особенность: если
Вы хотите, чтобы программа выполнилась до определенной строки, и устанавливаете курсор внутри модуля (Unit), то этого
не произойдет, Вы просто получите ошибку Cannot Run a Unit ,
и этим все закончится, т.к. IDE понимает это действие, как приказ запустить модуль, чего делать нельзя. Требуется
объяснить IDE, чего Вы от нее хотите примерно так: "Запусти основную программу,
и только потом выполни все, до текущего положения курсора". Для этого надо зайти в меню "Compile -> Primary File",
указать системе основной файл (НЕ модуль) Вашего приложения, и только после этого установить курсор внутрь модуля, и
нажать F4...
Казалось бы, что поменялось? А вот что: теперь IDE точно знает - основным файлом приложения является тот, который был
установлен, как Primary File, следовательно, совершенно нет необходимости запускать МОДУЛЬ, достаточно запустить
основной файл, и остановиться тогда, когда выполнение дойдет до нужной строки в модуле
Что такое окно Watches и как им пользоваться?
Оба средства вычисления и просмотра работают на уровне выражений, поэтому важно определить, что считается выражением.
Выражение состоит из констант, переменных и структур данных, скомбинированных с помощью операций и большинства встроенных
функций. Почти все, что вы можете использовать в правой части оператора присваивания, может также использоваться в качестве
отладочного выражения.
Элементы выражений отладчика
Элемент выражения | Допустимые значения |
Константы | Все допустимые типы: Boolean, Byte, Char, перечислимый тип, Integer, Longint, Real, Shortint, Word и строковый тип. |
Переменные | Все типы, включая типы, определенные пользователем. |
целочисленный тип | Любое целочисленное выражение с переменными границами диапазона. |
тип с плавающей точкой | Любые выражения с плавающей точкой или целочисленные выражения; лишние значащие цифры отбрасываются. |
символьный тип | Любое символьное выражение, включая печатаемые символы в одинарных кавычках, целочисленные выражения, тип которых приведен к типу Char, и контанты ASCII (#xx). |
булевский тип | True, False и все булевские выражения. |
перечислимый тип | Любые совместимые перечислимые константы или целочисленные выражения в рамках диапазона, тип которых приведен к совместимому перечислимому типу. |
указатель | Любые совместимые указатели или выражения с приведенными к ним типами; функция Ptr с соответствующим параметрами. |
строковый тип | Любая строковая константа (текст в одинарных кавычках); строковые переменные; строковые выражения, состоящие из конкатенированных строковых констант и переменных. |
множество | Любая множественная константа; любое выражение, совместимое с множественным типом, в котором используются операции +, - и *. |
Приведение типа | Соблюдаются стандартные правила Паскаля. |
Операции | Все операции Borland Pascal. |
Встроенные функции | Все функции, допустимые в выражениях-константах. |
Массивы | Массивы Borland Pascal - Mem, MemL, MemW. |
Вначале это окно нужно отобразить на экране. Делается это выбором пункта меню Debug -> Watch. На экране появится пустое окно с соответствующим заголовком. | |
Теперь запускаем программу в пошаговом режиме (нажатием клавиши F7, или выбором меню "Run -> Trace Into"),
в редакторе устанавливаем курсор на название переменной, за которой будем "следить", и жмем Ctrl+F7 (или меню
"Debug -> Add Watch...") и подтверждаем выбор нажатием "Ok"... Всё... Переменная добавлена в окно Watches,
и можно наблюдать за ее значением на каждом шаге выполнения программы... Примечание: Если выбрать переменную для просмотра без предварительного отображения пустого окна Watches на экране, ничего страшного не произойдет: IDE автоматически откроет окно Watches самостоятельно. |
i := 15;
значение переменной i будет уже равно не 0, а 15... |
const arr: array[1 .. 10] of integer = ( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 );
... то можно просмотреть одновременно (!!!) как весь этот массив, так и его левую и правую части... Для этого достаточно ввести во втором и третьем случае не только название массива, но и стартовый элемент, и (через запятую) число повторений | |
Кроме массивов и переменных встроенных типов возможно просматривать также и записи (либо в простом формате - только имя переменной, либо в специальном - с добавлением спецификатора R |
Символ | Тип, на который он влияет | Функция |
$, H или X | целочисленные типы | Шестнадцатиричный. Выводит целочисленные значения с префиксом $, включая те, которые содержатся в структуре данных. |
C | Char, строковые типы | Символьный. Выводит специальные символы для кодов ASCII 0..31. По умолчанию такие символы выводятся в виде значений #xx. |
D | целочисленные типы | Десятичный. Выводят целочисленные значения в десятичном виде (включая те, которые содержатся в структурах данных). |
Fn | с плавающей точкой | С плавающей точкой. Выводит n значащих цифр, где n лежит в диапазоне 2..18 (по умолчанию - 11). |
nM | все | Дамп памяти. Выводит n байт памяти, начиная с адреса, указываемого выражением. Если n не задано, то по умолчанию оно равно значению размера в байтах типа переменной. |
P | указатели | Указатель. Выводит указатели в формате сегм:смещ (а не Ptr(сегм:смещ), как это делается по умолчанию. |
R | записи, объекты | Запись. Выводит имена полей, например, (X:1;Y:10; Z:5) вместо (1, 10,5). |
S | Char, строки | Строки. Выводит символы ASCII 0..31 в виде #xx. Использует только для модификации дампов памяти (см. выше nM). |
Вычисление выражений
Чтобы вычислить выражение, выбираем команду Debug -> Evaluate/Modify или нажимаем
клавиши Ctrl+F4. Отладчик выводит диалоговое окно Evaluate and Modify (Вычисление и модификация). Здесь
(в поле ввода Expression) можно занести любое допустимое выражение, или выбрать его из списка ранее
вычисленных при помощи этого же диалога выражений и отредактировать. После нажатия на Enter или щелчка мышью на кнопке Evaluate (Вычислить), текущее значение выражения будет показано в поле Result (Результат). |
|
Примечание: если в поле Expression введено выражение, которое не может быть вычислено интегрированной средой, то в поле Result появится вот такая надпись: "Cannot evaluate this expression" |
Модификация переменных
Кроме этого, во время отладки с помощью диалогового окна Evaluate and Modify существует возможность поменять значение переменной на любое другое, чтобы проверить работу программы с другим, вновь присвоенным значением переменной. Для этого нужно установить курсор на имени переменной, и выбрать в меню "Debug -> Evaluate/Modify..." (или просто нажать Ctrl+F4)... В появившемся окне в поле Expression (Выражение) достаточно ввести имя переменной (или ничего не делать, если имя уже введено), и нажать кнопку Evaluate... Текущее значение выбранной переменной будет отображено в поле Result. Если оно же отображается и в поле New Value, это значит, что у Вас есть возможность прямо сейчас поменять это значение на любое другое (разумеется, совместимое по присваиванию с типом выбранной переменной; присваивать строке целому числу Вам никто не позволит, также, как и, например, записать в целочисленную переменную значение 3.14159), и применить новое значение переменной, не перезапуская программу...var i: integer; begin for i := 1 to 20 do writeln(i); readln; end., и Вам захотелось попасть в точку, где распечатывается значение i = 20 (желание далеко не самое сумасшедшее, представьте, например, что вместо простого распечатывания значения i там будет вызов какой-нибудь функции, которую надо прогнать пошагово при определенном значении i, да еще и функция может быть длинной и долго выполняться), но не очень хочется нажимать F7 почти 20 раз и приходить в нужную точку "естественным путем". Что делаем? А вот что:
Добавляем значение i в окно Watches, начинаем выполнять программу пошагово (F7)...
Я остановился, когда значение i было равно трем, хотя это же самое можно было сделать и сразу после входа в цикл,
то есть как только i стало равно единице. Теперь ставим курсор под название переменной i и жмем Ctrl+F4 для вызова нужного нам окна... Состояние экрана, которое должно получиться, видно на скриншоте... |
|
Переходим в поле New Value (как я говорил, если в нем записано текущее значение переменной,
то нам будет позволено его сменить), печатаем в нем значение 20, и жмем на "Modify"... Как видим, значение i в Watches тоже поменялось на 20, теперь спокойно закрываем окно "Evaluate and Modify" (кнопкой Cancel), и продожлжаем выполнять программу пошагово... |
Таким образом, мы пропустили все НЕинтересующие нас в данном случае шаги, выполнение которых могло занять значительное время...
Окно Call Stack - что это такое?
function fact(n: longint): longint; begin if n = 0 then fact := 1 else fact := n * fact(n - 1); end; function b(n: longint): integer; begin b := 2 * fact(n); end; begin writeln( b(10) ); end.Если начать выполнять ее пошагово, очень скоро можно запутаться в том, что именно сейчас выполняется (факториал какого числа вычисляется в данный момент, к примеру), и что еще осталось НЕзавершенным. А если программа будет в 10 раз длиннее, и будет содержать не 2, а 10 процедур/функций? В таком случае достаточно вызвать окно Call Stack (способ вызова - выше), и посмотреть, что именно в данный момент выполняется (то есть находится на першине стека вызовов), и что еще выполняться будет (находится ниже в списке), вплоть до Program, обозначающего, самый нижний уровень - основную программу...
Вот, например, в данном случае: выполняется функция вычисление факториала 8, которая вызвана из нижестоящей в списке функции вычисления факториала 9, которая в свою очередь... Ну, дальше все понятно... При продолжении пошагового выполнения этой программы в окно Call Stack будут добавляться новые и новые вызовы Fact(7), Fact(6), ... до Fact(0); потом рекурсия начнет раскручиваться обратно... |
Кстати, Call Stack - незаменимый помощник именно при работе с рекурсивными подпрограммами, поскольку дает возможность контролировать последовательность вызовов рекурсивной функции, и на ранней стадии определить, например, бесконечную рекурсию...
Например, здесь - совершенно очевидно, что рекурсивная функция оформлена неверно (отсутствует ветка Else), сразу после того, как в окне Call Stack появились вызовы f(-1) и f(-2)... |
Что такое точки останова программы?
Точка останова - это обозначенная в коде программы позиция, в которой вы хотите прекратить выполнение программы и вернуть выполнение отладчику. В этом смысле точка останова работает аналогично команде GoTo Cursor, при которой программа выполняется обычным путем до достижения определенной точки. Основное различие состоит в том, что вы можете задать несколько точек останова и точки останова, которые будут срабатывать не при каждом их достижении.
Допустим, есть следующая программа:const parameter = 10; function f(x: integer): integer; begin f := 10 * parameter - sqr(x); end; var i, j: integer; arr: array[1 .. 20] of real; begin for i := 1 to 20 do begin j := f(i); arr[i] := (15 * j / parameter) + i / j - 8; end; { ... } end.(не обольщайтесь, программа может быть не в 17, а в 1700 строк длиной, и функция f может быть гораздо более сложной, так же как и способ ее вызова)... Естественно, запустив эту программу, получаем:
Runtime error 200 at 0006:0004., из чего заключаем, что где-то в программе происходит деление на 0. Ну, где оно происходит, понятно. А вот на какой итерации? Чему равно i, при котором происходит эта ошибка? Можно, конечно, воспользоваться старым и проверенным способом, и сделать так:
const parameter = 10; function f(x: integer): integer; begin f := 10 * parameter - sqr(x); end; var i, j: integer; arr: array[1 .. 20] of real; begin for i := 1 to 20 do begin j := f(i); writeln('j = ', j); arr[i] := (15 * j / parameter) + i / j - 8; end; { ... } end., после чего нам будут распечатаны все значения j, для которых программа отработала нормально, и еще одно, при котором как раз и произошло "Деление на Ноль". Но для этого нужно вносить изменения в программу. А можно обойтись без её изменений... Используя точку останова...
Далее - установим курсор на той строке программы, где происходит деление, и выберем в
меню пункт "Debug -> Add Breakpoint..." Экран примет вид, показанный на снимке справа. Примечание: строка, на которую устанавливается BreakPoint, должна содержать выполняемый код и не может быть комментарием, описанием или пустой строкой... |
|
В поле Condition внесем условие, при котором следует остановить программу (условие вводится так же как и при использовании условных операторов в программе, только If здесь присутствовать не должно), и подтверждаем установку BreakPoint-а нажатием кнопки "Ok": |
Строка, в которой установлен хотя бы один BreakPoint (а устанавливать можно несколько точек останова для программы, причем даже на одной строке, но с разными условиями, может быть установлено более одного BreakPoint-а) меняет цвет на красный...
Если теперь запустить программу (обычным способом - через Ctrl+F9, или пошагово, не имеет значения), то как только значение j в выделенной строке станет равным 0, прогон программы будет приостановлен с выдачей вот такого сообщения: |
Как видим (в окне Watches), происходит это при i = 10... Что и требовалось определить...
Чтобы просмотреть, какие BreakPoint-ы были установлены в программе, достаточно зайти в меню "Debug -> BreakPoints",
и будет выведен список всех точек останова для данной программы:
Нажатием на "Clear All" можно удалить все точки останова сразу, "Delete" удалит только подсвеченный BreakPoint, а "Edit"-ом можно отредактировать текущий (подсвеченный) BreakPoint - подкорректировать условие, поменять номер строки, в которой должна производиться проверка, изменить счетчик числа проходов (задание для точки останова счетчика проходов сообщает отладчику, что останавливать программу нужно не при каждом достижении точки останова, а только на n-ый раз. То есть, если счетчик проходов равен 3, то отладчик останавливает программу только при третьем достижении данной точки останова) и т.д. |
Повторное выполнение (сброс программы)