Г Л А В А 5. ОТЛАДКА ПРОГРАММ ПОЛЬЗОВАТЕЛЯ В TURBO PASCAL. Turbo Pascal предлагает сверхусовершенствованную среду, с автоматическим управлением проектом, модульной организацией программ, высокой скоростью компиляции, с легко используемыми оверлеями. Но даже используя все эти предоставленные средства, программы пользователя могут содержать ошибки, которые приводят к неправильной работе программы. В помощь пользователю Turbo Pascal предоставляет средства, необходимые для отладки его программы, способствующие устранению всех ошибок в программе, ее тщательному тестированию и выполнению. Turbo Pascal позволяет легко определять местоположение ошибок во время компиляции и во время выполнения программы, а также позволяет включать или выключать автоматический контроль ошибок во время выполнения программы. Особенно важно то, что Turbo Pascal имеет мощный и гибкий отладчик исходного уровня, который позволяет пользователю выполнять программу построчно, просматривать выражения и модифицировать переменные по мере необходимости. Отладчик встроен в интегрированную среду разработки (IDE) Turbo Pascal; пользователь может редактировать, компилировать и отлаживать программу даже не выходя из Turbo Pascal. Для больших или сложных программ, которые требуют использования всего диапазона средств отладки от машинного языка до вычисления выражений Паскаля, Turbo Pascal полностью поддерживает автономный отладчик фирмы Borland, Turbo Debugger. Типы ошибок. Существует три основных типа программных ошибок: ошибки времени компиляции, ошибки времени выполнения и логические ошибки. Ошибки компиляции. Ошибки компиляции или синтаксические ошибки встречаются, когда забывают объявить переменную, передают ошибочное количество параметров процедуры, при назначении действительного значения целочисленной переменной. Это означает, что записываются операторы, которые не согласуются с правилами Паскаля. Turbo Pascal не закончит процесс компиляции программы пользователя (генерацию машинного кода), пока все синтаксические ошибки не будут удалены. Если Turbo Pascal обнаружит синтаксическую ошибку во время компиляции программы, он останавливает компиляцию, входит в исходный текст, указывает местоположение ошибки позиционированием курсора и выводит сообщение об ошибке в окно Edit. Как только пользователь исправит ошибку, он сможет начать процесс компиляции снова. Если используется версия командной строки (TPC.EXE), Turbo Pascal будет выводить ошибочный оператор, номер строки и сообщение об ошибке. Затем пользователь может войти в любой используемый им редактор, найти заданную строку, исправить ошибку и перекомпилировать программу. Для дополнительной информации о сообщениях об ошибках см. Приложение А в Руководстве программиста. Ошибки времени выполнения. Другой тип ошибок - ошибки времени выполнения программы или семантические ошибки. Они встречаются, когда пользователь компилирует синтаксически корректную программу, которая пытается сделать что-нибудь запрещенное во время ее выполнения, например, открывает несуществующий файл для ввода или производит деление на 0. В этом случае Turbo Pascal выводит на экран следующее сообщение об ошибке: Runtime error ## at seg:ofs (Ошибка выполнения # в сегменте:смещение) и останавливает выполнение программы пользователя. При использовании интегрированной среды Turbo Pascal определяет местоположение ошибки выполнения автоматически, осуществляя переход в окно редактирования для соответствующего исходный файл. Если пользователь выполняет программу в среде MS-DOS, он будет возвращаться в MS-DOS. Пользователь может загрузить модуль TURBO.EXE и использовать опции Search/Find error для локализации позиции ошибки в исходной программе (убедитесь, что опция Destination установлена в Disk). Для обнаружения ошибки пользователь может также использовать и опцию /F для компилятора командной строки (TPC.EXE). (Более полное объяснение опций командной строки TPC.EXE приведено в главе 9 "Компилятор командной строки".) Логические ошибки. Программа пользователя может содержать и логические ошибки. Это означает, что программа делает то, что ей указали вместо того, что хотелось бы. Может отсутствовать инициализация переменной; могут оказаться ошибочными вычисления; рисунки, изображенные на экране, выглядят неправильно; программа может просто работать не так, как было задумано. Такие ошибки находятся с большим трудом, и интегрированный отладчик поможет вам в этом случае наилучшим образом. Интегрированный отладчик Turbo Pascal. Некоторые ошибки времени выполнения (логические ошибки) незаметны и трудны для прослеживания. Другие ошибки могут скрываться за неуловимым взаимодействием разделов большой программы. В этих случаях необходимо интерактивное выполнение программы, во время которого производится наблюдение за значениями определенных переменных или выражений. Вам хотелось бы, чтобы Ваша программа останавливалась при достижении определенного места так, чтобы просмотреть, как она проработала этот кусок. Вам хотелось бы остановиться и изменить значения некоторых переменных во время выполнения программы, изменить определенный режим или проследить за реакцией программы. И вам хотелось бы сделать это в режиме, когда возможно быстрое редактирование, перекомпилирование и повторное выполнение программы. Интегрированный отладчик Turbo Pascal имеет все описанные выше возможности и даже более того. Он представляет собой встроенную часть интегрированной усовершенствованной среды Turbo Pascal (IDE): для использования предлагаются две основные функции меню (Run, Debug), а также некоторые клавиши для команд отладчика. Для дополнительной информации об IDE горячих клавишах см. главу 7 "Справочник по IDE" или справочную информацию о Turbo Pascal. Что может делать отладчик. Интегрированный отладчик работает очень просто. Ему не требуются специальные инструкции в Вашем коде, он не увеличивает размер Вашего .EXE файла и не требует перекомпиляции для создания отдельного .EXE файла после окончания отладки. Если Ваша программа разделена на ряд модулей, исходный код каждого из них автоматически загружается в редактор при трассировке. Если Вы используете оверлеи, отладчик автоматически обрабатывает их внутри IDE, которая выполняет переключения между компилятором, редактором и отладчиком. Обзор возможностей отладчика: Трассировка. F7 Run/Trace Into Вы можете выполнить одну строку вашей программы, затем прерваться и посмотреть на результаты. При вызове процедуры или функции внутри вашей программы, Вы можете задать режим выполнения вызова как одного шага или режим трассировки этой процедуры или функции строка за строкой. Вы можете так же трассировать вывод Вашей программы строка за строкой. Вы можете так же установить, чтобы экран переключался по необходимости или использовать два монитора. Вы можете так же установить экран вывода в отдельном окне. Переход на курсор. F4 Run/Go to Сursor Вы можете передвинуть курсор на определенную строку в Вашей программе, а затем указать отладчику выполнить программу до достижения этой строки. Это позволяет обходить циклы или другие утомительные участки программы, это также позволяет перебираться в то место программы, откуда Вы хотите начать отладку. Прерывание. С помощью команды Debug/Breakpoints Вы можете пометить строки в Вашей программе как точки прерывания. Когда в процессе выполнения Вашей программы достигается точка прерывания, выполнение программы приостанавливается и отображается исходный текст и курсор останавливается на строке с точкой прерывания. Затем Вы можете проверить значения переменных, начать трассировку или выполнить программу до другой точки прерывания. Вы можете подключить условие к точке прерывания. Вы можете также прерваться в любой точке Вашей программы, нажав клавишу Ctrl-Break. Произойдет остановка на следующей строке исходной программы, как если бы в этой строке была установлена точка прерывания. Наблюдение. Debug/Watches Пользователь имеет возможность задавать для просмотра в окне Watch некоторые объекты (переменные, структуры данных, выражения). Просматриваемые данные меняются, отражая текущие изменения в программе при пошаговом выполнении. Вычисление/модификация. Ctrl-F4 Debug/Evaluate/Modify Пользователь может вызвать окно Evaluate, что проверить значения переменных, структуру данных и выражения в интерактивном режиме. Используя окно Evaluate, Вы можете изменить значение любой переменной, включая строки, указатели, элементы массива и поля записей. Это обеспечивает простой механизм для проверки, как Ваш код реагирует на определенную установку значений или условий. Поиск. Пользователь может быстро находить объявления процедур или функций, даже если программа разбита на несколько модулей (Search/Find Рrocedure). Во время трассировки Вы можете быстро вернуться обратно из вызовов процедур или функций и проверить параметры каждого вызова (Window/Call Stack). Подготовка к использованию отладчика. До начала отладки Вы должны понимать, что основным элементом выполнения в отладчике является строка, а не оператор. Более точно наименьшим элементом выполнения является строка. Если на одной строке находится несколько операторов, они будут выполняться вместе при нажатии F7. С другой стороны, если один оператор размещен на нескольких строках, то при нажатии F7 будет выполняться весь оператор. Все команды выполнения основываются на строках, включая пошаговую отладку и точки прерывания; строка, на которой находится выполнение, всегда отмечена курсором выполнения. Прежде, чем начать отладку программы, Вы должны задать для компилятора Turbo Pascal инструкцию по генерации таблицы символов и таблицы номеров строк этой программы. Таблица символов представляет собой небольшую базу данных со всеми используемыми идентификаторами - константами, типами, переменными, процедурами и информацией о номерах строк. Директивы компилятора $D+ и $L+ делают это по умолчанию; они соответствуют элементам меню Options/Compiler/Debug Information и Options/Compiler/Local Symbols. Так же по умолчанию установлена опция Options/Debugger/Integrated, которая генерирует отладочную информацию в выполнимом файле. Директива {$D+} генерирует таблицу номеров строк, которая устанавливает соответствие между объектным кодом и исходным модулем. Директива {$L+} генерирует локальную отладочную информацию, а именно, строит список идентификаторов, локальных для каждой процедуры или функции, для того, чтобы отладчик мог хранить информацию о них в процессе отладки. Когда Вы используете директивы компилятора, разделяйте их запятыми и без пробелов, и ставя $ только перед первой директивой; например {$D+,L+}. Примечание: Вы можете отключить эти переключатели для сохранения памяти или дискового пространства во время компиляции. Когда Вы выполняете пошаговую отладку, Turbo Pascal будет иногда переключаться на экран пользователя, выполнять Ваш код, а затем возвращаться в интегрированную среду, ожидая следующей команды. Вы можете управлять переключением экрана с помощью установок Options/Debugger/Display Swapping, которые могут принимать 3 значения: - Smart: Это режим по умолчанию. Среда IDE переключается на экран пользователя, когда программа обращается к видеопамяти или при вызове программы. - Always: Переключение на экран пользователя происходит на каждом шаге. - None: Переключение экранов не происходит. Интегрированная среда остается видимой все время. Если в программе предусматривается вывод на экран или требуется ввод информации, текст будет писаться на экране среды. Вы можете восстановить окна интегрированной среды, выбирая Ё/Refresh Display. Начало сеанса отладки. Наиболее быстрый способ начать отладку состоит в загрузке программы и выборе команды Run/Trace Into (F7). Программа будет компилироваться. Когда компиляция завершится, редактор отобразит на дисплей тело основной программы с индикацией строки выполнения на первом операторе begin. Пользователь может начать трассировку программы с этого места (нажать клавиши F7 или F8) или использовать другие методы которые приведены ниже. Если пользователю необходимо начать отладку с определенного места программы, он может выполнить программу до этого места, а затем остановиться. Для этого, загрузите нужный раздел исходного модуля в редактор и передвиньте курсор на строку, где Вы желаете остановиться. Затем можно поступить двумя способами: - Выбрать команду Run/Goto Cursor (или нажать клавишу F4), которая будет выполнять программу пользователя до достижения строки, помеченной курсором, а затем останавить работу программы. - Задать на указанной строке точку прерывания (выбрать команду Debug/Toggle Breakpoint или нажать на Ctrl-F8), затем выполнить программу (выполнить команду Run/Run или нажать Ctrl-F9); остановка будет происходить каждый раз при достижении заданной строки. Вы можете задать несколько точек прерывания, в этом случае программа будет делать остановку всякий раз при достижении какой-либо из этих точек. Рестарт сеанса отладки. Если в процессе отладки программы возникает необходимость начать все сначала, то нужно выполнить команду Program Reset из меню Run. Система отладки повторно инициализируется, и команда следующего шага вернет вас к первой строке главной программы. При этом производится закрытие всех файлов, которые были открыты программой, очистка стека всех вложенных программ, которые вызывались программой, и освобождение всего использованного пространства кучи. Переменные программы, однако, не будут повторно инициализированы или подвержены модификации какого-нибудь другого вида. (Turbo Pascal никогда не инициализирует переменные автоматически). Однако, начальные значения типированных констант программы будут восстановлены. Turbo Pascal также предлагает рестарт, если Вы производите какие-либо изменения в программе во время отладки. Например, если Вы изменяете часть программы, а затем выбираете любую команду выполнения (нажимаете клавиши F7, F8, F4, Ctrl-F9 и т.д.), Вы получите сообщение : Source modified, rebuild? (Y/N) (исходный модуль модифицирован, нужно повторить сборку? да/нет ). Если Вы отвечаете Y, Turbo Pascal будет перекомпилировать программу и возобновит отладку программы с начала. Если Вы ответите N, Turbo Pascal предполагает, что Вы уверены в своих действиях, и продолжает сеанс отладки дальше. (Любые изменения в программе, которые Вы произвели, не будут влиять на ее выполнение до тех пор, пока Вы не перекомпилируете программу). Если Вы добавили или удалили строки программы, курсор выполнения не реагирует на эти изменения, и может оказаться, что будет выделяться ошибочная строка. Окончание сеанса отладки. В процессе отладки программы Turbo Pascal хранит трассу того, что Вы делаете и где находитесь в данный момент. Так как пользователь может в процессе отладки загружать и даже редактировать различные файлы, Turbo Pascal не интерпретирует загрузку другого файла в редактор, как конец сеанса отладки. Поэтому, если вы желаете выполнить или отладить другую программу, нужно выполнить команду Run/Program Reset (клавиша Ctrl -F2). Трассировка Вашей программы. Простейшая техника отладки - это пошаговая отладка, которая трассирует внутри процедур и функций. Загрузите программу RANGE.PAS в Turbo Pascal. {$D+,L+} {Для того, чтобы обеспечить полную генерацию отладочной информации} {$R-} {Для того, чтобы отключить проверку диапазона} program RangeTest; var List:array [1..10] of integer; Indx:integer; begin for Indx:=1 to 10 do List[Indx]:=Indx; Indx:=0; while (Indx<11) do begin Indx:=Indx+1; if List[Indx]>0 then List[Indx]:=-List[Indx] end; for Indx:=1 to 10 do writeln(List[Indx]); end. Начните отладку, нажав клавишу F7. Это пошаговая команда. Turbo Pascal произведет компиляцию автоматически, а затем подготовится к пошаговой обработке этой программы. Заметим, что курсор выполнения расположен на операторе begin (строка 7). Помните, что курсор выполнения помечает следующую строку программы, которая должна быть выполнена. Нажмите клавишу F7 несколько раз. Курсор выполнения переместится на оператор List[Indx]:=Indx и остановится. Это значит, что строка выполняется в цикле. Выберите команду Debug/Watches/Add Watch (Ctrl-F7) для просмотра в окне Add Watch. Вы можете просматривать значения переменных, структур данных или выражений в окне Watch. То, что появится в окне Add Watch зависит от того, где располагается курсор, когда Вы нажимаете на клавишу Ctrl-F7. Если курсор расположен на первой букве любой алфавитно-цифровой строки, внутри строки или сразу за ней, строка будет копироваться в окно Add Watch и подсвечиваться. Так, если курсор был спозиционирован на слове Indx, то Indx появится в окне. Если в окне необходимо что-либо изменить, начните набор на клавиатуре и первоначальное выражение и подсветка исчезнут. Как только появится окно Add Watch, независимо от его содержимого, можно добавить в него текст, если нажать клавишу Ў (которая копирует дополнительный текст из редактора). Поместите List в окно, используя Ў, и нажмите Enter. Тогда в окне Watch в нижней части экрана появится строка: List : (1,2,0,0,0,0,0,0,0,0) Cнова нажмите клавишу Ctrl-F7, наберите слово Indx и нажмите Enter. Indx будет первым в списке в окне Watch: Indx : 3 List : (1,2,0,0,0,0,0,0,0,0) Нажмите клавишу F7 снова и Вы увидите, что значения Indx и List в окне Watch изменятся, отражая работу Вашей программы. Как только Вы войдете в цикл while, Вы снова увидите, что значения Indx и List изменяются шаг за шагом. Заметим, что эти изменения в окне Window отражают действия каждой строки цикла после нажатия клавиши F7. Продолжайте нажимать на клавишу F7, пока не достигнете начала цикла while, c Indx равным 10. Во время прохождения через цикл, Вы можете наблюдать как изменяются значения в окне Watch. Когда выполняется оператор List [ Indx ] := - List [ Indx ]; значение Indx изменится на -11. Если Вы продолжаете нажимать на F7, то обнаружится, что вы вошли в бесконечный цикл. Таким образом, если Вы наберете такую программу, она будет компилироваться и выполняться. Получается бесконечный цикл, так как цикл while выполняется 11 раз, а не 10, и последнее значение переменной Indx равно 11. Так как массив List содержит только 10 элементов, значение List(11) будет указывать на некоторую позицию памяти вне массива List. Из-за способа распределения переменных уже окажется, что значение List(11) займет в памяти тоже место, что и переменная Indx. Это значит, что при Indx=11, запись: List [Indx] := - List [Indx] идентична записи Indx := -Indx. Так как значение переменной Indx равно 11, этот оператор изменит ее значение на -11. В результате в программе начнется повторное выполнение цикла. Этот цикл теперь изменяет дополнительные байты в месте, соответствующем List[-11..0]. И т.к. значение Indx никогда не будет заканчивать цикл со значением большим или равным 11, то цикл никогда не закончится. Важно отметить то, что используя лишь две клавиши (F7 и Ctrl - F7), через несколько минут, Вы быстро и легко прослеживаете промежуточные значения переменных и находите ошибку. Пошаговое выполнение программы. Различие между командами Trace Into (F7) и Step Over (F8) в том, что при использовании F7 осуществляется трассировка внутри процедур и функций, в то время как использование F8 приведет к обходу вызовов подпрограмм. Эти команды имеют особое значение при выполнении оператора begin основной программы, если программа использует модули, имеющие раздел инициализации. В этом случае, использование F7 приведет к трассировке раздела инициализации каждого модуля, что позволяет увидеть, что инициализируется в каждом модуле. При использовании F8 эти разделы не будут трассироваться, и курсор выполнения переходит на следующую строку после begin. Рассмотрим следующий (неполный) пример программы: ($D+,L+) program TestSort; const NLMax=100; type NumList=array[1..NLMax] of integer; var List : NumList; I,Const : word; procedure Sort ( var L:NumList; Cnt:Integer); begin (sort the list) (сортировка списка) end; (of proc sort) (процедуры Sort) begin randomize; Count:=NLMax; for I:=1 to Count do List[I] := Random(1000); sort(List,Count); for I:=1 to Count do Write(List[I] :8); Readln end. {программы TestSort} Предположим, что Вы отлаживаете процедуру Sort. Вы хотите осуществить трассировку процедуры Sort, включая проверку значения внутри List до вызова Sort. Однако, выполнять 100 раз инициализацию внутри List очень утомительно. Есть ли способ выполнять цикл, не останавливаясь на каждой выполняемой строке. Да, фактически, существует несколько способов. Во-первых, Вы могли бы выделить этот цикл в отдельную процедуру и нажать клавишу F8 для того, чтобы обойти ее трассировку, но это слишком нерационально. Во-вторых, Вы могли бы установить внутри программы точку прерывания. Мы объясним, что это за точки прерывания, и как они используются немного позже. В конце концов, Вы могли бы использовать команду Run/Go to Cursor (F4). Переместите курсор на строку с вызовом Sort, а затем нажмите на клавишу (F4). Ваша программа будет выполняться до достижения строки, помеченной курсором. Курсор выполнения переместится на эту строку; затем Вы можете начать трассировку с этого места, нажимая на клавишу F7, для того, чтобы можно было сделать трассировку внутри Sort. Команда Run/Goto Cursor (F4) действует на вложенных уровнях вызовов подпрограмм, даже если их исходный код находится в другом файле. Например, Вы могли бы разместить курсор где-либо внутри процедуры Sort и нажать на клавишу F4; программа выполнялась бы до этой строки. По существу, Sort могла бы быть выделена в отдельный модуль, отладчик бы уже знал, когда нужно остановиться и что отобразить. Существуют три случая, когда команда Go to Cursor (F4) не будет выполнять программу до отмеченной курсором строки. Первый, когда Вы расположили курсор между двумя выполняемыми строками; например, на пустой строке или строке с комментариями. В этом случае программа будет выполняться до следующей строки, содержащей оператор, который может быть выполнен. Второй случай, когда курсор расположен вне процедурного блока, например, на операторе объявления переменной или операторе program. Отладчик будет выводить сообщение "no code generated for this line" (для этой строки код не генерируется). Третий случай, когда Вы располагаете курсор на строке, которая никогда не выполняется. Например, строка располагается выше курсора выполнения (предполагается, что вы находитесь не в цикле) или строка является частью else - условного оператора, когда выражение if имеет значение true. В этом случае отладчик будет действовать так, как если бы выполнялась команда Run/Run (Ctrl-F9); программа будет выполняться до конца или до точки прерывания. Предположим, что Вы трассируете процедуру Sort,затем хотите завершить работу программы и посмотреть выходные результаты. Каким способом сделать это? Сначала нужно переместить курсор к последнему оператору end основной части программы, а затем выполнить команду Run/Go to Cursor (F4). Или проще, нужно выполнить команду Run/Run (Ctrl-F9). Она позволяет отладчику продолжить нормальное выполнение программы пользователя. Программа будет выполняться до конца, или до тех пор, пока Вы не достигнете точки прерывания или не будет нажат Ctrl-Break. Использование точек прерывания. Точки прерывания являются важным инструментом отладки. Точка прерывания подобна знаку остановки, введенному в программу пользователя. Когда программа встречает такую точку, она останавливает свое выполнение и ожидает дальнейших отладочных инструкций. Примечание: Вы можете иметь до 16 активных точек прерывания. Заметим, что точки прерывания существуют только во время сеанса отладки; они не сохраняются в файле .EXE, если программа компилируется на диск. Чтобы задать точку прерывания, используйте обычные команды редактирования для перемещения курсора на каждую строку программы, где Вы хотите сделать паузу. Каждый раз выполняйте команду Debug/Toggle Breakpoint (Ctrl-F8). Когда строка отмечается как точка прерывания, она высвечивается. Это не должна быть пустая строка, комментарии, директивы компиляции; объявления констант, типов, меток, переменных; заголовком программы, модуля, процедуры или функции. Как только Вы задали точки прерывания, выполняйте программу с помощью команды Run/Run (клавиша Ctrl-F9). Сначала программа будет выполняться нормально. Когда встретится точка прерывания, программа остановится. Соответствующий исходный файл (основная программа, модуль или включенный файл) загружается в окно Edit, которое визуализируется на экране и курсор выполнения помещается на строку с точкой прерывания. Заметим, что точка прерывания не высвечивается, когда на ней находится курсор выполнения. Если какие-либо переменные или выражения были добавлены в окно Watch, то они также выводятся на дисплей со своими текущими значениями. Затем, пользователь может использовать любой режим отладки. - Вы можете осуществлять пошаговое выполнение программы, используя команду Run / Trace Into, Step Over или Go to Cursor (F7, F8 или F4). Вы можете проверить или изменить значения переменных. - Вы можете добавить или удалить выражения из окна Watch. - Можно назначить или удалить точки прерывания. - Можно просмотреть выходные результаты программы, используя команду Windows/User Screen (Alt-F5). - Вы можете перезапустить программу сначала ( Run/Program Reset и, затем, команду пошагового выполнения). - Можно продолжить выполнение до следующей точки прерывания (или до конца программы), выполнив команду Run/Run (Ctrl-F9). Для удаления точки прерывания из строки переместите курсор на данную строку и, выполнив команду Debug/Toggle Breakpoint (или нажмите Ctrl-F8) еще раз. Эта команда включает или отключает точку прерывания в строке; если она используется для строки с точкой прерывания, то строка становится нормальной. Давайте вернемся к примеру, который был рассмотрен ранее. begin {основная часть программы Test.Sort} Randomize; Count := NLMax; for I := 1 to Count do List [I] := Random (1000); Sort ( List,Count ); for I := 1 to Count do Write ( List [I] : 8 ); Readln end. {программа Test.Sort} Как уже говорилось, идея была в том, чтобы обойти первоначальный цикл и начать трассировку с вызова процедуры Sort. Новый вариант. Передвиньте курсор на строку с вызовом процедуры и выполните команду Debug/Toggle Breakpoint ( Ctrl-F8), которая отметит строку, как точку прерывания. Теперь выполните программу до этой точки, используя команду Run/Run (Ctrl-F9 ). Когда программа достигнет этой строки, она остановится и позволит Вам начать отладку. Использование Ctrl-Break. Кроме назначения точек прерывания, пользователь может сделать немедленную остановку во время выполнения программы, используя клавишу Ctrl-Break. Это означает, что можно прервать работу программы в любое время. Когда Вы нажимаете на клавишу Ctrl-Break, выполнение программы прекращается. Вы возвращаетесь в редактор, курсор выполнения расположен на следующей строке программы, и программа готова к дальнейшему пошаговому выполнению. Фактически, отладчик автоматически подключает DOS, BIOS и другие сервисные функции. Он знает, является ли текущий выполняющийся код программой DOS, программой BIOS или программой пользователя. Когда Вы нажимаете на клавишу Ctrl-Break, отладчик ждет, пока программа выполняется сама. Затем он делает пошаговое выполнение инструкций машинного уровня, пока следующая инструкция не будет в начале строки исходного кода на Паскале. С этого момента отладчик прекращает работу, перемещает курсор выполнения на эту строку и предлагает Вам нажать на клавишу ESC. Примечание: Если пользователь нажимает на клавишу Ctrl-Break второй раз еще до того, как отладчик находит и отображает следующую строку исходного кода для выполнения, то отладчик завершает работу и не пытается найти строку исходного кода. В этом случае, процедуры выхода не выполняются, что означает, что файлы, видеорежим и распределение памяти DOS могут быть не полностью очищены. Просмотр значений. Выполнение программы предоставляет много информации, но не в том объеме, как хотелось бы. Может возникнуть необходимость просмотреть за тем, как изменяются значения переменных во время выполнения программы. Рассмотрим процедуру Sort из предыдущей программы: procedure Sort ( var L : NumList; C : word ); var Top,Min,k : word; Temp : integer; begin for Top := 1 to C-1 do begin Min := Top; for k := Top+1 to C do if L[k] Top then begin Temp := L[Top]; Top := L[Min]; L[Min] := Temp; end; end; end; {процедуры Sort} Примечание: Измените NLMax в теле программы на 10 так, чтобы Вы могли работать с меньшим массивом. В этой процедуре есть ошибки, будем просматривать ее (используя команду Run/Trace Into или клавишу F7) и наблюдать за значениями переменных L, Top, Min и k. Отладчик дает возможность пользователю задать объекты для просмотра во время выполнения Вашей программы. Как Вы и предполагаете, объектами просмотра являются переменные, структуры данных и выражения, расположенные в окне Watch, где отображаются их текущие значения, обновляемые по мере выполнения каждой строки программы. Давайте вернемся к предыдущему примеру. Установить объекты наблюдения просто. Передвигайте курсор к каждому идентификатору и выполняйте команду Debug/Watch/Add Watch (Ctrl -F7) для добавления каждого выражения в окно Watch. Результат может выглядеть так: k : 21341 Min : 51 Top :21383 L : (163,143,454,622,476,161,850,402,375,34) Предполагается, что Вы только что вошли в процедуру Sort, курсор выполнения расположен на начальном операторе begin. (Если Вы еще не вошли в процедуру Sort, то для каждого выражения в окне Watch будет появляться сообщение "unknown identifier" (неизвестный идентификатор), пока Вы не войдете в процедуру). Заметим, что переменные K, Min и Top имеют произвольные значения, т.к. они еще не были инициализированы. Значения переменной L, предположительно, тоже должно быть произвольным; они не будут таковыми при выполнении всей программы; все они должны быть неотрицательными и лежать в интервале от 0 до 999. Если нажать на клавишу F7 четыре раза, то мы продвинемся к строке if L[k]Top then, назад к вершине внешнего цикла, и снова вниз к строке if L[k] < L[Min] then. В этот момент окно Watch будет выглядеть следующим образом (для L даны предыдущие значения): k : 3 Min : 2 Top : 2 L : ( 34,143,454,622,476,161,850,402,375,34 ) Теперь Вы можете заметить две вещи. Первое, последнее значение переменной L(34), которое является также и наименьшим значением, скопировалось в первое значение L, и значение, которое уже было там раньше (163), исчезло. Второе, переменные Min и Top имели одинаковые значения во время трассировки процедуры. Фактически, Вы можете заметить, что переменная Min получает значение переменной Top, но она никогда не изменяется где-либо еще. Однако, ниже цикла располагается проверка: if Min<>Top then. Или эта проверка ошибочна, или существует какое-то несоответствие между этими двумя частями процедуры. Оказывается, ошибочна пятая строка программы. Она должна выглядеть так: Min := k, вместо L[Min] := L[k]. Исправьте строку, переместите курсор к первоначальному оператору begin в процедуре Sort и выполните команду Run/Go to Cursor (F4). Так как Вы изменили программу, на экране появится окно с вопросом Source modified, rebild? (Y/N) (исходный модуль модифицирован, нужна ли сборка? Да/Нет). Ответьте Y. Программа будет перекомпилироваться, начнется ее выполнение, затем произойдет остановка на начальном операторе begin процедуры Sort. Теперь программа работает верно. Значение первого элемента теперь не меняется на наименьшее из значений элементов массива L, происходит обмен-перемещение значения первого элемента массива на место, где до этого располагался элемент с наименьшим значением. Затем процесс повторяется со второй позицией, третьей и т.д., пока список элементов не будет отсортирован полностью. Типы выражений просмотра. Вы можете помещать туда любой тип константы, переменной или структуру данных; Вы можете также вставлять и выражения Паскаля. Ниже приведено описание данных, которые Вы можете помещать в окно Watch и то как они будут там отображаться. ─────────────────────────────────────────────────────────────────── Выражения Вывод ─────────────────────────────────────────────────────────────────── Целые Десятичные или шестнадцатиричные. Например: -23 $10 Вещественные Без экспоненты, если возможно. Примеры: 38328.27 6.283Е23 Символы Печатаемые: в одиночных кавычках (включая псевдографику) так, как они есть. Управляющие: ASCII коды. Примеры: 'b' '0' #4 Булевские True или False. Перечислимые Действительные имена значений (прописные) значения Пример: RED JAN WEDNESDAY Указатели Сегмент:смещение в шестнадцатиричном формате. Пример: PTR($3632,$106) PTR(DSEG,$AB) PTR(CSEG,$220) Строки В одиночных кавычках. Пример: 'Droid' Массивы В круглых скобках, разделенные запятыми. Многомерные массивы в виде вложенных списков. Примеры: (-42,23,2292,0,684) Записи В круглых скобках, поля разделяются запятыми. Вложенные записи как вложенные списки. Примеры: (5,10,'Borland',RED,TRUE) Объекты Так же, как записи. Выражения, допустимые для записей так же допустимы для объектов. Множества В квадратных скобках и разделяются запятыми. Используются поддиапазоны, когда это возможно. Пример: [MON,WED,FRI] ['0'..'9','A'..'F'] Файлы В формате (status,fname), где status - CLOSED, OPEN,INPUT или OUTPUT, а fname - имя дискового файла, назначенное файловой переменной. Пример: (OPEN,'BUDGET.DTA') ─────────────────────────────────────────────────────────────────── Спецификаторы формата. Для точного управления тем, как будет отображаться информация в окне Watch, Turbo Pascal позволяет добавлять к Вашим Watch выражениям спецификаторы формата. При наличии такого спецификатора, он следует за выражением Watch, отделенный от него запятой. (Для отладки Вам не требуется спецификатор формата; это более сложная возможность.) Спецификатор формата состоит из необязательного счетчика повторений типа Integer, за которым следует ноль или несколько символов формата, а между символом формата и числом повторений не требуется пробелов. Спецификаторы формата приведены в разделе меню Debug главы 7. Счетчик повторения используется для того, чтобы отобразить последовательные переменные, такие как элементы массива. Например, предположим, что List - массив из 10 целых чисел, тогда Watch выражение для List отражало бы следующее: List: (10,20,30,40,50,60,70,80,90,100) Если Вы желаете просмотреть элементы массива в определенном диапазоне, то Вы можете определить индекс первого элемента и добавить счетчик повторений: List [6],3:60,70,80 Это полезно в тех случаях, когда имеются слишком большие массивы и их отображение на одной строке невозможно. Применение счетчиков повтора не ограничивается одними массивами, за любой переменной может следовать счетчик повтора. Общий синтаксис var,x задает отображение х последовательных переменных с таким же типом, как у переменной var, начиная с адреса переменной var. Заметим, однако, что счетчик повтора игнорируется, если Watch выражение не обозначает переменную. Существует хорошее правило, которое состоит в том, что данная конструкция является переменной, если она может появляться в левой части оператора присваивания или использоваться в качестве var параметра в процедуре или функции. Продемонстрируем использование спецификаторов формата. Предположим, что были объявлены следующие типы данных и переменные: type NamePtr = ^NameRec; NameRec = record Next:NamePtr; Count:Integer; Name:string[31]; end; var List:array[1..10] of integer; P:NamePtr; При наличии приведенных выше объявлений могут быть сконструированы следующие Watch выражения: List:(10,20,30,40,50,60,70,80,90,100) List[6], 3H:$30,$46,$50 P:PTR($ЗЕАО,sc) P,P: ЗЕАО;000C P^:(PTR(S3EF2,S2),412,"John") P^,RS:(NEXT:PTR($3EF2,S2);count:$19c;NAME:"John") P^.NEXT^,R:(NEXT:NIL;count:377;NAME:"Joe") Mem[$40:0],10M:F8 03 F1 02 00 00 00 00 BC 03 Mem[$40:0],10MD:248 3 248 2 0 0 0 0 188 3 Приведение типов. Приведение типов является еще одним мощным средством, которое можно использовать для модификации отображения выражений в окне Watch, что позволяет интерпретировать одни и те же данные в качестве данных разных типов, отличных от их исходного представления. Это особенно полезно при работе с адресами и указателями, для того чтобы рассматривать их как указатели на определенный тип данных. Предположим, что Ваша программа содержит переменную DFile, которая имеет тип file of MyRec и Вы выполняете следующую последовательность операторов: Assign(DFile,"INPUT.REC"); Reset(DFile); Если Вы добавляете переменную DFile в окно Watch, то соответствующая строка в окне Watch будет выглядеть следующим образом: DFile:(OPEN,"INPUT.REC") Возможно, что пользователь пожелает получить более полную информацию о записи файла. Если изменить программу так, чтобы она могла использовать модуль Dos, то Вы сможете модифицировать выражение для DFile в выражении FileRec(DFile),rh. Это означает: "Отобразить переменную DFile таким образом, как если бы она была записью типа FileRec (объявленного в модуле Dos), при этом все поля записи помечаются и все целые значения отображаются в шестнадцатиричном коде". Тогда результат в окне Watch может иметь следующий вид: FileRec(DFile),rh:(HANDLE:$6;MODE:$D7B3;RECSIZE:$14;PRIVATE:($0, $0,...)) Данная запись является слишком длинной для полной ее визуализации. Возможно, однако, использовать клавиши, передвигающие курсор, для просмотра невидимых на экране данных (см. раздел "Редактирование и удаление в окне Watch"). Благодаря приведению типа, пользователь может теперь просмотреть определенные поля DFile. Например, Вы могли бы просмотреть поле UserData, добавляя выражение для FileRec(DFile).UserData в окно Watch. FileRec(DFile).UserData:(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0) Пользователь может применять тот же прием к структурам данных и типам, разработанным им самим. Если они были объявлены в программе или модуле, то пользователь может осуществить приведение их типов в окне Watch. Правила приведения типов приведены в главе 6 " Выражения " Руководства программиста. Выражения. Как уже ранее упоминалось, пользователь может использовать выражения как Watch выражения. Это могут быть вычисления, сравнения, адреса со смещением и другие подобные выражения. В таблице 5.1 приведен список типов разрешенных компонент Watch выражений, а также их допустимые значения Таблица 5.1. Значения Watch выражений. ─────────────────────────────────────────────────────────────────── Разрешенные компоненты Допустимые значения ─────────────────────────────────────────────────────────────────── Литералы и Константы Все стандартные типы данных: Boolean, Byte, Char, перечис- лимый, Integer, LongInt, Real, ShortInt, String, Word. Переменные Все типы, включая типы, опре- деленные пользователем, и элементы структур данных. целочисленный тип Любое целочисленное выраже- ние в пределах диапазона зна- чений переменной. с плавающей точкой Любое выражение с плавающей точкой (или целочисленное) в пределах диапазона экспоненты; лишние значащие цифры подавляются; символьный тип любое символьное выражение, включая любой печатаемый символ в одиночных кавычках; целочисленное выражение преобразуется в символьное с помощью функции Chr или Char; константы ASCII (знак # с последующим значением от 0 до 255). логический тип True и False; любое логичес- кое выражение. перечислимый тип Любая совместимая перечисли- мая константа, целочисленное выражение без допустимого ди- апазона приводится к сов- местимому перечислимому типу данных. указатель Любой совместимый указатель, любое выражение совместимого типа, функция Ptr (c cответс- твующими параметрами); строка Любая строковая константа (текст,заключенный в одиноч- ные кавычки); строковые пере- менные, строковые выражения, состоящие из строковых конс- тант и переменных, сцепленных с помощью оператора +. множество Любая константа множества (совместимые элементы в квад- ратных скобках), любое сов- местимое выражение множества, включая использование опера- торов +, -, *. Приведение типа Следуют стандартным правилам Паскаля. Операторы Все стандартные операторы Паскаля, плюс выражения Turbo Pascal, такие как xor, @ и т.д. Встроенные функции Abs, Addr, Chr, Cseg, Dseg, Hi, IOResult, Length, Lo, MaxAvail, MemAvail, Odd, Meml, MemW, Ofs, Ord, Pred, Ptr, Round, Seg, SizeOf, SPtr, SSeg, Succ, Swap, Trunc. Массивы Mem, MemL, MemW ─────────────────────────────────────────────────────────────────── Другими словами, выражение должно быть нормальным разрешенным выражением. Оно может включать все перечисленные в таблице 5.1 компоненты. Дополнительная информация по модификации Watch выражений приведена в разделе "Вопросы модификации". Редактирование и удаление в окне Watch. Очень просто редактировать, добавлять и удалять выражения для просмотра. Когда окно Watch активно, текущее активное выражение подсвечивается. Чтобы выбрать другое выражение, используйте клавиши Home, End, °, ∙. Чтобы редактировать (изменять) текущие выражения, Вы можете выбрать Debug/Watches/Edit Watch. Можно сделать еще проще - просто нажать Enter. Отладчик открывает окно с выбранным выражением и Вы можете редактировать его. Вы уже знаете как добавлять выражение для просмотра, но когда окно Watch активно, есть более простой способ: нажмите Ins. Появится окно. Вы можете набрать выражение, добавить к нему с помощью клавиши ў, или принять то, что скопировалось из позиции курсора по умолчанию. Примечание: Вы не можете изменить значение выражения, а только само выражение. Чтобы изменить значение, используйте Debug/Evaluate /Modify. Чтобы удалить текущее выражение выберите Debug/Watches/Delete Watch или просто нажмите Del. Чтобы удалить все выражения, выберите Debug/Watches/Remove All Watches. Вычисление и модификация. Использование окна Watch очень удобно для трассировки значений по мере продвижения по программе. Однако, иногда Вы не испытываете в этом необходимость. Часто встречаются переменные и выражения, которые необходимо проверить только в определенной точке или точках программы. Вам действительно не хотелось бы, чтобы эти переменные находились в окне Watch постоянно. При этом довольно утомительно добавлять такие переменные в окно Watch , а затем сразу же их удалять, особенно если их несколько. Наконец, может также возникнуть необходимость не просто просмотреть значения переменной, но и изменить эти значения. Для обеспечения таких возможностей отладчик предлагает окно Evaluate/Modify. Чтобы вызвать его, выполните команду Debug/Evaluate/Modify (или нажмите клавишу Ctrl-F4). Окно имеет три поля, помеченные словами Evaluate, Result и New Value. Также как и окно Add Watch, окно Evaluate/Modify уже содержит слово, найденное под курсором, оно находится в режиме выделения. Его можно редактировать подобно редактированию в Add Watch, а когда нужно вычислить выражение, нажмите на клавишу Enter. Тогда в поле Result появится текущее значение константы, переменной или выражения. Поле Evaluate допускает точно такой же набор констант, переменных и выражений, что и окно Watch. Вы имеете те же возможности и ограничения, о которых мы уже упоминали. Можно использовать также и спецификаторы формата, которые определены для Watch выражения. Когда Вы нажимаете на клавишу Enter, в поле Evaluate выделяется идентификатор или выражение. Если Вы начнете набирать новое имя (не нажимая на клавишу Ins или клавиши со стрелками), то оно будет размещаться на месте старого имени. Это позволяет быстро набирать серию переменных и выражений. Поле New Value позволяет модифицировaть значения переменных, чьи имена расположены в поле Evaluate. Можно вводить константу, имя другой переменной или даже выражение. Результирующее значение должно иметь тип, совместимый с переменной из поля Evaluate. Однако, если выражение в поле Evaluate не является положением памяти, то ввод в поле New Value приведет к появлению сообщения: Cannot be modified (модификация невозможна). Поле Result отображает текущее значение выражений поля Evaluate в том же формате, что и окно Watch. При этом иногда данные могут быть слишком велики для визуализации их в поле Result, подобно окну Watch. В этих случаях Вы можете использовать клавиши Tab, Backtab, Ў, ў, Home и End, чтобы перемещаться по полю. Во всех случаях вы можете использовать клавишу ° и ∙ (или стандартные клавиши редактирования), для перемещения по этим трем полям. Как только Вы заканчите модификацию поля, нажмите на клавишу Enter для вычисления ввода. Модификация выражений. Возможность модификации переменной во время выполнения программы приносит огромную помощь в процессе отладки. Но она и таит в себе опасность. Так что Вам нужно точно знать, что нужно делать при модификации, а что делать не рекомендуется. Наиболее простым способом модификации, конечно, является ввод имени переменной в поле Evaluate и ввод соответствующего значения в поле New Value. Когда вы нажимаете на клавишу Enter после того как набрали новое значение, значение переменной изменяется, соответственно обновляется и поле Result. Можно вводить не только константы. В поле New Value можно вводить любую переменную или выражение, которые можно вводить и в поле Evaluate, с одним важным ограничением: должна быть совместимость присваивания с переменной или выражением в поле Evaluate. Другими словами, если expr1 является текущим в поле Evaluate, то нельзя вводить выражение expr2 в поле New Value, если оператор expr1:= expr2 будет приводить к ошибке компиляции или времени выполнения. Заметим, что обратное утверждение не всегда верно. Бывают случаи, когда оператор expr1:=expr2 является верным, но Вы все равно не можете использовать выражение expr2 в поле New Value. Если введенное вами выражение имеет несовместимый тип (например, если ввести значения с плавающей точкой для переменной типа integer, в поле Result появится сообщение: Type mismatch (несоответствие типов). Для того, чтобы произвести повторное отображение текущего значения переменной в поле Result, вернитесь обратно в поле Evaluate и нажмите на клавишу Enter. Если введенное выражение дает значение, выходящее за допустимый дипазон, (например, если ввести значение 50,000 для переменной типа Integer), то в поле Result будет отображаться сообщение: Constant out of range (константа выходит за допустимый диапазон). То же самое будет происходить, если Вы задаете элемент массива, индекс которого находится вне области значений индексов. Если введенное в поле New Value выражение является выражением, которое не может быть присвоено, то в поле Result появится сообщение: Cannot evaluate this exspression (невозможно вычислить выражение). Такие выражения включают массивы, записи, множества и файлы. Аналогично, если переменная или выражение из поля Evaluate не может быть модифицировано (весь массив, запись, множество или файл), то попытка присвоить ему значение будет вызывать сообщение: Cannot be modified (модификация невозможна). Что можно модифицировать? Обратитесь к таблице 5.1, где приводится список всех компонент и их допустимых значений, которые можно использовать в Watch выражениях. Помните, что выражения могут использовать только встроенные функции, перечисленные как доступные для Watch выражений в таблице 5.1. Также необходимо помнить следующее: - Нельзя модифицировать массивы, записи или файлы полностью. Однако, Вы можете модифицировать отдельные элементы или записи, которые разрешаются в один из типов, перечисленных в таблице 5.1, при условии, что они сами не являются массивами или записями. - Нельзя осуществить прямую модификацию нетипированных параметров, передаваемых в процедуру или функцию. Однако, Вы можете осуществить для них приведение типа к данному типу, а затем модифицировать их, согласно ограничениям, которые уже приводились. - Не забывайте, что при модификации переменных существует определенная опасность. Например, если Вы изменяете указатель, то это может привести к таким изменениям в памяти, которые не подразумевались, возможно, даже произойдет изменение других переменных и структур данных. Поиск. Когда производится отладка большой программы, особенно состоящей из нескольких модулей, Вы можете перестать понимать или, по крайней мере, так углубиться, что не будете понимать, как лучше достигнуть требуемого места программы. Чтобы помочь Вашему продвижению по программе, отладчик предлагает две команды: Window/Call Stack и Search/Find Procedure. Стек вызовов. Каждый раз при вызове процедуры или функции Turbo Pascal запоминает вызов и параметры, переданные этой процедуре или функции. Когда Вы выходите из процедуры или функции, то вызов выталкивается из стека и происходит возврат в вызывающую программу. Если выполнение Вашей программы прерывается из-за точки прерывания или по команде пошагового выполнения, Вы можете запросить просмотр текущего стека вызова, используя команду Window/ Call Stack (Ctrl-F3). При этом немедленно возникает окно, в котором отображается список вызовов процедур/функций, являющихся активными в стеке в данный момент. Стек вызова выполняет еще и другую важную функцию: он позволяет прослеживать в обратном порядке последовательность вызовов. Когда Вы вызываете стек первый раз, то подсвечивается самый верхний вызов. Для перемещения по стеку вверх или вниз можно использовать клавиши со стрелками. Нажав пробел, Вы будете возвращаться в последнюю активную точку внутри данной программы или подпрограммы. В качестве примера рассмотрим программу TESTPOWER.PAS: program TestPower; function Power(Base,Exp:word):longint; begin if Exp<=0 then Power:=1 else Power:=Base*Power(Base,Exp-1) end;(of func Power) begin(main body of TestPower) Writeln('2^14=',Power(2,14)) end.{of program TestPower} Компилируйте TESTPOWER.PAS, установите точку прерывания на второй строке функции Power (строка Power:=1). Теперь выполните программу. Когда она сделает остановку, выполните команду Window/Call Stack. Вы можете использовать стрелки ° и ∙ для передвижения через вызовы. Стек вызовов хранит до 128 вложенных вызовов. Примечание: На CGA отображается 9 вызовов, на Hercules, EGA, VGA отображаются 12 вызовов. Поиск процедур и функций. Иногда, в процессе отладки, требуется найти определенную процедуру или функцию для того, чтобы установить точку прерывания, выполнить программу до этой точки, проверить список параметров, просмотреть переменные или по ряду других причин. Если исходный код разделен на несколько файлов, то требуется команда Search/Find Procedure. Когда Вы выполняете эту команду, появляется небольшое окно, предлагающее ввести имя процедуры или функции. После того, как Вы наберете его и нажмете на клавишу Enter, Turbo Pascal проверяет свои внутренние таблицы и находит место, где располагается заданная подпрограмма, загружает соответствующий исходный файл (если необходимо) и переводит Вас в окно Edit, при этом курсор позиционируется на начале процедуры или функции. При использовании команды Search/Find Procedure необходимо помнить о следующем: - Команда Find Procedure не влияет на текущее состояние отладки. Другими словами, если Вы ранее останавливались в некоторой точке программы, Вы будете продолжать находиться в этой точке, и выполнение программы Run/Trace Into (F7) будет приводить к выполнению этой текущей строки программы, а не процедуры или функции, чье местоположение Вы только что определили. - Команда Find Procedure размещает курсор на первой выполнимой строке этой процедуры или функции, а не на их заголовках. Это означает, что Вы можете выполнить команду Run/Go to Cursor (F4), чтобы программа выполнялась от текущей строки до начала найденной процедуры или функции. - Вы можете использовать эту команду только в том случае, если программа была откомпилирована, и если информация отладки доступна для процедуры или функции. При выполнении команды Find Procedure может возникнуть неопределенность при задании имени, так как возможно наличие подпрограмм с одним и тем же именем в различных местах Вашей программы (в модулях, внутри других программ и т.д.). Вы можете уточнять имя подпрограммы, предворяя его именем модуля или подпрограммы, содержащей его, а также именем любой процедуры или функции, включающей эту программу. Например, module.proc..proc. Если Вы модифицируете исходный код и местоположение файла (или даже имя), процедуры или функции изменились, команда Search/Find Procedure не воспринимает эти изменения до перекомпиляции программы. Если Вы сначала компилируете программу TestPower (см. раздел "Стек вызовов"), затем удаляете пустую строку, которая располагается над объявлением функции Power, команда Search/Find Procedure расположит курсор на операторе if...then вместо оператора begin. Объектно-ориентированная отладка. Примечание: Вам не нужно делать никакой специальной подготовки для отладки объектно-ориентированных программ. Работа с объектами в IDE включает две функциональных возможности: пошаговая отладка и трассировка вызовов методов, и проверка полей объектов. Интегрированная отладка понимает объекты и управляет ими автоматически в таком же стиле, как управляет отладкой процедур и записей. Пошаговая отладка и трассировка методов. Вызов метода обрабатывается IDE, как вызов обычной процедуры или функции. Нажатие F8 (Step Over) обрабатывает вызов метода как невидимый блок и выполняет его без отображения внутреннего кода метода; в то время, как нажатие F7 (Trace Into) загружает код метода, если он доступен и трассирует операторы метода. Нет отличий между трассировкой вызовов статических методов и трассировкой вызовов виртуальных методов. Вызов виртуального метода разрешается во время выполнения программы, и, так как отладка происходит во время выполнения, здесь нет неясности и встроенный отладчик всегда знает какой именно метод будет вызываться сейчас. Окно Call Stack отображает имена методов с предшествующими типами объектов, которые определяли эти методы (например: Circle.Init, а не просто Init). Объекты в окне Evaluate. Объекты отображаются в окне Evaluate/Modify в стиле, подобном отображению записей. Разрешаются все те же спецификаторы формата. И все выражения, допустимые для записей, допустимы и для объектов. Когда имя объекта находится в окне Evaluate, будут отображаться только поля данных. Однако, когда вычисляется имя указанного метода, например ACircle.MoveTo значение отображенного указателя является адресом кода метода. Это справедливо и для статических, и для виртуальных методов. Интегрированный отладчик управляет виртуальным методом посредством просмотра таблицы виртуальных методов (VMT), и адрес виртуального метода для данного экземпляра объекта дает адрес кода метода для этого экземпляра. Когда идет трассировка внутри метода, IDE знает сферу действия параметра Self. Вы можете вычислить или просмотреть Self, а так же указать Self со спецификаторами формата и с квалификаторами полей или методов. Выражения в команде Find Procedure. Turbo Pascal разрешает выражение в команде Find Procedure (меню Search). Для того, чтобы быть правильным, выражение должно вычисляться в адрес в сегменте кода. Заметим, что это применимо к параметрам и переменным типа процедуры, а не только к методам объекта. Общие вопросы. Вы изучили как использовать отладчик, давайте рассмотрим некоторые вопросы, которые могут возникнуть в процессе отладки. Как писать программы для отладки. Существует ряд простых вещей, которые могут облегчить отладку Вашей программы. Для большинства случаев справедливо то, что не следует располагать на одной строке программы более одного оператора. Так как выполнение программы в процессе отладки происходит строка за строкой, это требование будет обеспечивать выполнение не более одного оператора каждый раз, когда Вы нажимаете клавишу F7. В то же время допускаются и случаи, когда можно разместить на строке несколько операторов. Если есть список операторов, которые нужно выполнить, но которые фактически не имеют отношения к отладке, то Вы можете произвольно располагать их на одной или двух строках так, чтобы их можно было быстрее пройти при пошаговом выполнении. Вот почему в одном из ранее приведенных примеров мы написали: W:=10;X:=20;Y:=30;Z:=40; вместо W:=10; X:=20; Y:=30; Z:=40; Вам также можете располагать объявления переменных так, чтобы операторы объявления переменных, которые, вероятно, будут размещаться в окне Watch, были бы ближайшими к первоначальному оператору begin процедуры или функции. Когда Вы трассируете процедуру или функцию, Вы можете быстро перемещать курсор по списку, используя команду Add Watch (Ctrl-F7) для добавления каждой переменной в окно Watch. Подобным образом, при наличии выражений, значения которых в определенных точках программы Вы хотите разместить в окне Watch или вычислить, вставляйте их как комментарии. Когда Вы достигнете такой точки, Вы можете передвинуть курсор в начало выражения и скопировать его в окно Add Watch или Evaluate/Modify. Это особенно полезно в случае сложного выражения, включающего приведение типов, символа формата, элемента массива или поля записи. В конце концов лучшей отладкой является профилактическая отладка. Хорошо разработанная, ясно написанная программа может иметь не только немного ошибок, но и будет облегчать для Вас трассировку и фиксацию местоположения этих ошибок. Существует несколько основных положений, о которых следует помнить при составлении программы: - программируйте с постепенным наращиванием. При возможности кодируйте и отлаживайте программу небольшими секциями. Прорабатывайте каждую секцию до конца прежде чем переходить к следующей; - разбивайте программу на части: модули, процедуры, функции. Избегайте построения процедур или функций, размер которых больше 25 строк, в противном случае разбивайте их на несколько более меньших процедур или функций; - старайтесь передавать информацию только через параметры, вместо использования глобальных переменных внутри процедур и функций. Это поможет Вам избежать побочных явлений и облегчит отладку программы, так как Вы сможете легко прослеживать всю информацию, входящую и выходящую из заданной процедуры или функции; - не торопитесь. Сосредоточьте действия на том, чтобы программа работала правильно, прежде чем предпринимать шаги по ускорению ее работы. Вопросы, связанные с памятью. При отладке больших программ может возникнуть нехватка памяти. Turbo Pascal одновременно хранит в памяти редактор, компилятор, отладчик, текущий файл исходного кода, выполняемый код и таблицы символов, а также другую отладочную информацию. Вы можете управлять объемом свободной памяти, используя команду File/Get Info. И в среде и в самом компиляторе очень много возможностей для конфигурации и Вы можете получить больше свободной памяти для компиляции и отладки Вашей программы. Одни решения очень просты, другие сложнее и вызывают генерацию другого кода или выборочное отключение отладочной информации. Всегда начинайте с самых безболезненных и надежных и затем, если необходимо, предпринимайте более радикальные шаги. Чтобы найти конфигурацию системы, которая Вас вполне удовлетворит, Вам может понадобиться изменение Ваших AUTOEXEC.BAT, CONFIG.SYS, TURBO.TP и TURBO.EXE файлов. Примечание: Изменения, сделанные Вами в диалоговом окне Startup Options (Options/Environment) сохраняются прямо в TURBO.EXE. Изменения, сделанные с помощью других диалоговых установок, могут быть сохранены в файле конфигурации TURBO.TP. См. главу 7. Внешние программы. - Удалите резидентные программы. Если у Вас загружены Sidekick или SuperKey, выйдете из среды, удалите их и снова загрузите TURBO. EXE. - Измените CONFIG.SYS для удаления ненужных драйверов (ANSI.SYS, SMART.DRV и т.п.). Вы можете также сократить число файлов и буферов, изменив FILES= и BUFFERS=. Убедитесь, что эти изменения надежны для всех программ, которые Вы используете. Реконфигурация Turbo Pascal. 1. Установите Compile/Destination в Disk. 2. В диалоговом окне Options/Linker установите Link Buffer в Disk. Примечание: Существуют опции командной строки, которые можно передать в TURBO.EXE и которые слоответствуют всем установкам диалогового окна Options/Environment/StartUp. 3. Используя диалоговое окно Options/Environment/StartUp Options, попытайтесь сделать: а) Если Вы используете расширенную (EMS) память, на Вашем компьютере, убедитесь, что опция Use Expanded Memory включена и Turbo Pascal доступно достаточное количество EMS (сократив размер EMS используемой резидентными программами или драйверами, такими как кэш диски, SideKick и т.д.). IDE может использовать по крайней мере 400К EMS для оверлеев, дополнительных буферов и других системных ресурсов. Все это увеличивает рабочую память под Вашу программу. (Выделение более 400К EMS будет увеличивать производительность IDE, хотя это не предоставляет больше памяти для компиляции и отладки Вашей программы.) b) Если Вы не отлаживаете графическую программу, убедитесь, что опция Graphics Screen Save выключена. Как и все опции начальной загрузки, Вы можете указать эту опцию в командной строке при отладке графической программы. (Если у Вас есть доступная EMS, отключение этой опции не будет воздействовать на объем памяти в IDE.) с) Сократите значения в опциях Overlay и Window Heap Size. Если у Вас есть доступная EMS, сокращение этих размеров не будет отрицательно влиять на производительность IDE. d) Отключите опцию Load TURBO.TPL. TURBO.TPL содержит стандартные модули и загружается в память при загрузке TURBO.EXE для оптимизации производительности редактора связей. Отключая эту опцию, Вы сможете компилировать и отлаживать программы, но Вам необходимо извлечь все модули из TURBO.TPL (используя утилиту TPUMOVER). Примечание: Если TURBO.TPL не загружен, Вы не сможете вычислять выражения, используя диалоговое окно Evaluate/Modify до тех пор, пока не начнется сеанс отладки. Как альтернатива, Вы можете оставить опцию Load TURBO.TPL включенной и сократить размер TURBO.TPL на 15К. Удалите из TURBO.TPL все модули за исключением SYSTEM.TPU. Примечание: Оставьте извлеченные модули на диске и установите Options/Directories/Unit Directories так, чтобы Ваша программа могла использовать модули Dos, Crt, Overlay, Printer. е) Выключите отладочную информацию в тех модулях, которые уже отлажены. Делайте это модуль за модулем. После того, как код реализован, оттестирован и отлажен, выключите символьную информацию в этом модуле, отключив Debug Information в диалоговом окне Options /Compiler и перекомпилируйте. Вы можете так же включить {$D-} в модуль. Если Вы сделаете так, хорошо использовать условные директивы и определять включение и выключение отладочной информации в различных модулях (см. главу 21 в Руководстве программиста.) Если Вы выполнили все, что написано здесь и отключили отладочную информацию где только можно в Вашей программе, и Вам все еще не хватает памяти, попробуйте модифицировать Ваш код как описано ниже. Примечание: Конечно, если Вы не отлаживаетесь, Вы можете значительно увеличить объем доступной памяти в IDE, отключив Integrated (Options/Debugger). Модификация исходного кода. Некоторые из следующих процедур очень просто выполняются и значительно увеличивают объем доступной памяти. Другие более радикальны и Вам может потребоваться использование условных директив (см. главу 21 в Руководстве программиста) для включения и выключения их. - Сделайте модули Вашей программы оверлейными. Это очень надежно, гибко и значительно увеличивает рабочее пространство IDE. См. главу 13 Руководства программиста. - Используя диалоговое окно Options/Memory Sizes, сократите Stack Size и Low Heap Limit. Убедитесь, что для работы Вашей программы достаточно стека, особенно, если Вы выключили проверку стека, как предлагается ниже. - Используя установки в диалоговом окне Compiler Options, попытайтесь сделать: * Выключите Range Checking и Stack Checking. По умолчанию Stack Checking включена. Выключайте ее только если Ваша программа стабильна и Вы определили ее требования к стеку. * Выключите Emulation во время отладки. Конечно, можно использовать только Emulation и 8087/80287, если Вы используете плавающую точку и IЕЕЕ. Если Вы используете на отладочной машине сопроцессор, отключите Emulation, когда Вы отлаживаете код с плавающей точкой. - Сократите число символов в интерфейсных частях модулей. Не объявляйте в интерфейсной части модуля то, что не используется вне модуля. Это практика надежного программирования, которая к тому же сохраняет память во время компиляции больших программ. IDE и Turbo Debugger. Сам Turbo Pascal и IDE предоставляют множество способов получить больше памяти, изменяя установки по умолчанию. Если Вы вышли за пределы памяти во время компиляции или отладки Вашей программы и испытали большинство из предложенных здесь решений, рассмотрите вопрос использования IDE для редактирования и компиляции Вашей программы и затем использования Turbo Debugger для ее отладки. Если у Вас есть Turbo Debugger и Вы хотите использовать его для отладки программ, разработанных в IDE, сконфигурируйте IDE следующим образом: 1. Установите Compile/Destination в Disk. 2. В диалоговом окне Options/Debugger отключите Integrated и включите Standalone. Вы можете также использовать компилятор командной строки TPC.EXE или компилятор командной строки для расширенной памятью TPCX. EXE для построения больших программ (в несколько мегабайт). Затем Вы можете использовать TD, TD286 или TD386 для их отладки. Рекурсивные подпрограммы. Рекурсия является средством программирования, при котором процедура вызывает сама себя (прямо или косвенно). Например, функция Power в рассмотренном ранее примере является рекурсивной, потому что она вызывает себя для вычисления значения, которое ей требуется для возврата. Существует ряд положений, о которых нужно помнить в процессе отладки рекурсивной подпрограммы. Во-первых, глубокие уровни рекурсии могут поглощать участки системного стекового пространства, что повлечет за собой побочные действия (например, остановку или аварийное завершение Вашей программы из-за переполнения стека). Есть основная опасность при использовании рекурсии при любых обстоятельствах. Будьте готовы также к тому, что если в процессе отладки Ваша программа аварийно завершается, то причиной этого скорее всего будет являться переполнение стека, а не что-нибудь, что делается с отладчиком. К тому же, если имеются глубокие уровни рекурсии, Вы, вероятно, не сможете осуществить немедленный выход, используя стек вызова, потому что стек вызова ограничен последними 128 вызовами функций/процедур. Вы можете, однако, опуститься на дно стека, используя его для нахождения самого первого вызова, выйти на этот уровень и использовать стек вызова снова. Каждый раз при вызове рекурсивной функции, создается новый набор локальных переменных и осуществляется передача параметров (кроме типа var) по значению. Если Вы добавили эти значения в окне Watch, то нужно учитывать, что они будут "плавать", отражая активные на данный момент локальные данные. Случаи, когда отладка невозможна. Существует ряд случаев, когда невозможно осуществить трассировку заданной процедуры или функции. Обычно (но не всегда) это происходит по причине недоступности исходного текста. Типичны следующие ситуации: - любая встроенная процедура или функция, т.е. любая процедура или функция типа inline. Это происходит из-за того, что нет вызова процедуры или функции вообще, на место вызова помещается соответствующий машинный код. Такой вызов трактуется как один оператор. Заметим, что Вы можете трассировать процедуры и функции, которые используют операторы inline, однако, в этом случае каждый оператор inline обрабатывается как одна строка, без учета того, сколько строк он занимает. То есть действует правило, такое же как для других операторов: если один оператор расположен на нескольких строках, то он обрабатывается как одна строка командами Run/Trace Into и Step Over (F7 и F8). - любая подпрограмма Turbo Pascal из одного из стандартных модулей (Crt, Dos, Graph, Graph3, Overlay, Printer, System, Turbo3). - любая внешняя (external) процедура или функция. - любая процедура или функция обработки прерывания (interrupt). - любая процедура, функция или код инициализации, содержащиеся в модулях, которые не были откомпилированы с использованием директивы {$D+} (или при включенном Options/ Compiler/ Debug Information в среде IDE). - любые процедура, функция или код инициализации, содержащиеся в модуле, чей исходный код нельзя найти. Если это не текущий справочник и не справочник модулей, или исходный код представляет собой файл, имя которого отлично от unitname.PAS (где unitname - имя модуля, заданное в предложении USES, IDE будет запрашивать Вас корректное имя файла. Если Вы введете нулевое имя файла или нажмете на клавишу ESC, отладчик будет работать так, как если бы отладочная информация была недоступна. - любая процедура, назначенная как процедура выхода. Если Вы трассируете программу с помощью команды Run/Trace Into (F7), Вы никогда не войдете в процедуру выхода. Однако заметим, что можно установить точку прерывания в процедуре выхода. Когда курсор выполнения достигнет этой точки прерывания, отладчик, соответственно будет делать прерывание. Типичные проблемы. Существует ряд проблем, которые часто возникают в процессе отладки. Для обзора приведем их перечень: - отсутствует генерация необходимой глобальной и локальной отладочной информации. По умолчанию, оба таких переключателя имеют значения On. Если у Вас есть проблемы, связанные с трассировкой программы или модуля, то расположите директивы {$D+,L+} в начале каждой программы или модуля, которые Вы желаете отладить. - начинается отладка другой программы, а точки прерывания и Watch выражения предыдущей программы не удалены. Прежде чем загрузить для отладки новую программу, Вы должны выполнить следующие команды: Run/Program Reset (Ctrl-F2), Debug/ Watches/ Remove All Watches. - попытка откомпилировать и выполнить программу, когда предыдущая программа еще является главным файлом. Используйте команду Compile/Main File для удаления предыдущего имени или установки нового. - нажмите клавишу N при получении сообщения Source modified, rebuild(Y/N). Это означает, что во время отладки был модифицирован исходный файл и таблицы отладчика с номерами строк стали несоответствовать исходному коду. Могут быть нарушены функции отладчика такие как, обработка точек прерывания, трассировка и т.д. Если Вы случайно набрали символ и затем удалили его, вероятно, нужно нажать клавишу N. Если были вставлены или удалялись строки, то лучше будет нажать клавишу Y, так как иначе отлаживаемый Вами машинный код не будет соответствоать исходному коду, который Вы просматриваете. Обработка ошибок. В дополнение к интегрированному отладчику Turbo Pascal предлагает несколько директив компиляции и свойств языка, чтобы помочь пользователю находить программные ошибки. В этом разделе описываются некоторые из этих возможностей. Вы можете сами вставлять проверку ошибок времени выполнения, запрещая автоматическую проверку ошибок и написав собственный код обработки ошибок. Рассмотрим примеры. Контроль ошибок ввода/вывода. Предположим, что Вы выполняете эту программу и ввели по запросу значения 45 и 8х, а затем нажали на клавишу Enter. Что произойдет? program DoSum; var A,B,Sum:integer; begin Write('Enter two numbers:'); (ввести два числа) Readln(A,B); Sum:=A+B; Writeln('The Sum is',Sum); (сумма равна) Readln end end. Вы получите ошибку времени выполнения (106) и курсор будет позиционирован на операторе Readln(A,B); Что произошло? Программа ожидала целочисленное значение, а Вы ввели нечисловое значение (8х), что и привело к ошибке выполнения. В короткой программе, подобно этой, такая ошибка не доставит много хлопот. Но что если при вводе длинного списка чисел, введя уже большую их часть, Вы сделали бы такую ошибку? Вам пришлось начать бы все сначала. Еще хуже, если бы Вы писали программу для того, чтобы ее использовали пользователи и выявились эти ошибки. Turbo Pascal предоставляет Вам возможность устранять автоматический контроль ошибок ввода/вывода и самим тестировать такие ошибки внутри программы. Чтобы выключить контроль ошибок ввода/вывода, нужно включить директиву компилятора {$I-} в программу (или режим Option/Compiler/I/O Checking). Такая директива компилятора запрещает построение кода для контроля ошибок ввода/вывода. Контроль на принадлежность допустимому диапазону. Другой типичный класс ошибок выполнения включает выход за допустимый диапазон или выход за граничные значения. Такое может произойти, например, при присваивании слишком большого значения целочисленной переменной, или попытки индексирования массива вне его границ. По Вашему желанию Turbo Pascal будет генерировать код для контроля ошибок выхода за допустимый диапазон. Это приведет к укрупнению программы и замедлению ее выполнения, но может быть неоценимым при трассировке любых ошибок выхода за границу в Вашей программе. Давайте еще раз рассмотрим один из предыдущих примеров: program RangeTest; var List:array[1.10] of integer; Indx:integer; begin for Indx:=1 to 10 do List[Indx]:=Indx; Indx:=0; While(Indx<11) do begin Indx:=Indx+1; if List[Indx]>0 then List[Indx]:=-List[Indx] end; for Indx:=1 to 10 do Writeln(List[Indx]) end. Если ввести эту программу, затем откомпилировать и выполнить ее, то она будет в бесконечном цикле. Чтобы понять причину этого, проанализируем данный код: цикл выполняется 11 раз, а не 10, и переменная Indx имеет значение 11 при последнем выполнении цикла. Каким образом проверять подобные случаи? Вы можете вставить директиву {$R+} в начале программы, чтобы включить проверку диапазона. Теперь при выполнении программа будет останавливаться с ошибкой времени выполнения (код 201 - ошибка выхода за допустимый диапазон, так как индекс массива выходит за допустимые границы) как только Вы попадете на оператор if List[Indx]>0 при значении Indx=11. Если Вы находились в интегрированной среде, то автоматически выдается этот оператор, и выводится сообщение об ошибке. Примечание: Опция Range Checking в диалоговом окне Options/Compiler по умолчанию выключена; включение этой опции делает Вашу программы значительно более общей и более медленной, но мы настоятельно рекомендуем использовать ее до полной отладки Вашей программы. Существуют такие ситуации, когда Вам, вероятно, потребуется нарушить границы, например, при работе с динамически распределенными массивами, или когда используются функции Succ и Pred для перечислимых типов данных. Можно установить контроль принадлежности допустимому диапазону выборочно, размещая в начале программы {$R-} директиву. Для каждой части программы, которая требует контроля, размещайте директиву ($R +) в начале этой части и директиву {$R-} в конце. Например, можно было бы написать предшествующий цикл следующим образом: while Indx < 11 do begin Indx := Indx+1 ($R+) (разрешение контроля принадлежности допустимому диапазону) if List [Indx] > 0 then List [Indx] := - List[Indx] ($R-) (выключение контроля принадлежности допустимому диапазону) end; Контроль принадлежности допустимому диапазону будет осуществляться только в операторе if...then, и нигде больше, конечно, если другие директивы {$R+} отсутствуют в программе. Другие возможности обработки ошибок. Turbo Pascal предоставляет пользователю возможность применять иные приемы обработки ошибок. Более подробно они описываются в других разделах данного руководства. Здесь же мы только кратко коснемся их. Когда Ваша программа заканчивается нормальным образом или с ошибкой времени выполнения, вызывается стандартная процедура выхода, которая связывается с Вашей программой. Turbo Pascal позволяет применять Ваши собственные процедуры выхода, которые вызываются перед стандартной. Фактически, каждый модуль может иметь собственную процедуру выхода так, что Вы можете иметь автоматический код очистки, также как и обычный автоматический код инициализации. Процедуры выхода более детально описываются в главе 18 "Вопросы контроля" Руководства программиста. Если Вы пытаетесь распределить память (с помощью New или GetMem), и нет достаточного количества памяти в куче, то автоматически вызывается процедура обработки ошибок кучи, которая просто заставляет закончиться Вашу программу с ошибкой времени выполнения. Можно, однако инсталлировать свою собственную процедуру обработки ошибок кучи, которая будет управлять данной ситуацией так, как Вам хочется. Например, удалить динамические структуры, которые больше не нужны, или просто сделать так, чтобы New и GetMem возвращали nil указатель. Процедуры обработки ошибок кучи более подробно описываются в главе 16 "Память" Руководства программиста. При использовании модуля Graph Вы можете выполнить контроль ошибок аналогично контролю ошибок ввода/вывода. Функция GraphError в этом модуле возвращает информацию об ошибке для многих графических подпрограмм. Глава 12 "Модуль Graph и BGI" Руководства программиста подробно описывает, как использовать эту процедуру и коды ошибок модуля Graph. Модуль Overlay содержит целочисленную переменную OvrResult, которая хранит код результата последней операции, выполненной монитором оверлеев. Подобным образом модуль Dos хранит коды результата в переменной DosError.