ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП |
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы. |
Часть 20 Полное дерево меню ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД На Рис. 13.1 показана полная структуру спускающихся меню Турбо отладчика TD. ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї іЁ File Edit View Run Breakpoints Data Options Window Help і АДВДДВДДВДДДДВДДДДВДДДДДДДВДДДДДДДДДВДДДДДДВДДДДДДДДВДДДДДДВДДДДЩ і і і і і і і і і і і і і і і і і і і АДДДДї і і і і і і і і АДДДДДДДДДДїі і і і і і і і АДДДДДДДДДДДДДДДДДДїіі і і і і і і АДДДДДДДДДДДДДДДДДДДДДДДДїііі і і і і і АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДїіііі і і і і АДДДДДДДДДДДДДДДДДДДДДДДДДДї ііііі і і і АДДДДДДДДДДДДї і ііііі і і АДДДДДДДДДДДДДДДДїі і ііііі і АДДДДДДДДДДДДДДДДДДїіі v ііііі АДДДДДДї ііі ЪДДДДДДДДДДДДДДДДДДДДДДДДДДї ііііі і ііі і Run і ііііі v ііі іДДДДДДДДДДДДДДДДДДДДДДДДДДі ііііі ЪДДДДДДДДДДДДДДДДДДїііі і Run F9 і ііііі і Ё (System) іііі і Go to cursor F4 і ііііі іДДДДДДДДДДДДДДДДДДіііі і Trace Into F7 і ііііі і Repaint Desktop іііі і Step Over F8 і ііііі і Restore Standard іііі і Execute to... Alt-F9 і ііііі іДДДДДДДДДДДДДДДДДДіііі і Until Return Alt-F8 і ііііі і About... іііі і Animate... і ііііі АДДДДДДДДДДДДДДДДДДЩііі і Back Trace Alt-F4 і ііііі ЪДДДДДДДДДДДДДДДДДДДДДДДЩіі і Instruction Trace Alt-F7 і ііііі і ЪДДДДДДДДДДДЩі іДДДДДДДДДДДДДДДДДДДДДДДДДДі ііііі і v і і Arguments... і ііііі і ЪДДДДДДДДДДДДДДДДДДї і і Program reset Ctrl-F2 і ііііі і і File і і АДДДДДДДДДДДДДДДДДДДДДДДДДДЩ ііііі і іДДДДДДДДДДДДДДДДДДі і ЪДДДДДДДДДДДДДДДЩіііі і і Open... і і і іііі і і Change dir... і і v ЪЩііі і і Get Info... і і ЪДДДДДДДДДДДДДДДДДДДДДДДДДДї і ііі і і і і і Breakpoints і і ііі і і і і іДДДДДДДДДДДДДДДДДДДДДДДДДДі і ііі і іДДДДДДДДДДДДДДДДДДі і і Toggle F2 і і ііі і і Symbol Load... і і і At... Alt-F2 і і ііі і і і і і Changed memory global... і і ііі і іДДДДДДДДДДДДДДДДДДі і і Expression true global...і і ііі і і Quit Alt-X і і і Handware Breakpoint... і і ііі і АДДДДДДДДДДДДДДДДДДЩ і і Delete all і і ііі і ЪДДДДДДДДДДДЩ АДДДДДДДДДДДДДДДДДДДДДДДДДДЩ і ііі і і ЪДДДДДДДДДДДДДДЩ ііі і v і ііі і ЪДДДДДДДДДДДДДДДДДДДї v ііі і і View і ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДїііі і іДДДДДДДДДДДДДДДДДДДі і Data іііі і і Breakpoints і іДДДДДДДДДДДДДДДДДДДДДДДДДДДДДіііі і і Stack і і Inspect... іііі і і Log і і Evaluateіmodify... Ctrl-F4 іііі і і Watches і і Add watch... Ctrl-F7 іііі і і Variables і і Function return іііі і і Module... F3 і АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩііі і і File... і ііі і і CPU і ЪДДДДДДДДЩіі і і Dump і ЪДДДДДДДДДДДДї і іі і і Registers і ЪДДі Module... і і іі і і Numeric Processor і і і Dump і і іі і і Execution History і і і File... і і іі і і Hierarchy і і АДДДДДДДДДДДДЩ і іі і і Windows messages і і і іі і і Another >іДДЩ і іі і АДДДДДДДДДДДДДДДДДДДЩ ЪДДДДДДДДДДДДДДДДДДДДДДДДЩ іі і ЪДДДДДДДДЩ ЪДДДДДДДДДЩі і v і і і ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДї v і і і Options і ЪДДДДДДДДДДДДДДДДДДДДїі і іДДДДДДДДДДДДДДДДДДДДДДДДДДДі і Window іі і і Language... Source і іДДДДДДДДДДДДДДДДДДДДіі і і Macros >іДДї і Zoom F5 іі і і Display options... і і і Next F6 іі і і Path for source... і і і Next pane Tab іі і і Save options... і і і Size/move Ctrl-F5 іі і і Restore options... і і і Iconsize/restore іі і АДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ і і Close Alt-F3 іі і ЪДДДДДДДДДДДДДДДДЩ і Undo close Alt-F6 іі і і іДДДДДДДДДДДДДДДДДДДДіі і v і Dump pane to log іі і ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДї і User screen Atl-F5 іі і і Create... Alt = і і 1 Module TPDEMO іі і і Stop recording Alt - і і 2 Watches іі і і Remove і АДДДДДДДДДДДДДДДДДДДДЩі і і Delete all і ЪДДДДДДДДДДДДДЩ і АДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ і АДДДДДДДДДДДДДДї v і ЪДДДДДДДДДДДДДДДДДДДДДДДї v і Help і ЪДДДДДДДДДДДДДДДДДДДДДДДї іДДДДДДДДДДДДДДДДДДДДДДДі і Edit і і Index Shift-F1 і іДДДДДДДДДДДДДДДДДДДДДДДі і Previous topic Alt-F1 і і Copy Shift-F3 і і Help on help і і Paste Shift-F4 і АДДДДДДДДДДДДДДДДДДДДДДДЩ і Copy to Log і і Dump pane to log і АДДДДДДДДДДДДДДДДДДДДДДДЩ Глава 14. Отладка программы ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Отладка программы аналогична всем другим этапам реализации программы - это наполовину искусство, наполовину наука. Существу- ют специальные процедуры, которые можно использовать для отслежи- вания ошибки, однако, чтобы сократить этот процесс, требуется также хорошая интуиция. В большинстве отлаживаемых вами программ лучшее, что вы мо- жете сделать - это быстро найти источник ошибок в исходном коде. Для этого нужно освоить соответствующие методы, а также изучить такие способы, которые позволят избежать повторного появления ошибок. Мы начнем с того, что посмотрим, с чего можно начать отладку программы, которая не работает должным образом. В данной главе мы обсудим также различные подходы к отладке, разные типы ошибок, которые могут встречаться в программе, и предложим методы проверки программы, позволяющие убедиться в пра- вильности ее работы. Давайте посмотрим, с чего можно начать, когда программа не работает корректно. Когда что-то не работает ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Прежде всего не следует впадать в панику. Даже наиболее опытные программисты редко пишут программы, которые начинают ра- ботать с первого раза. Чтобы избежать напрасной траты времени на долгие и бесплод- ные поиски ошибки, постарайтесь побороть стремление случайно уга- дать, где находится ошибка. Лучшим методом здесь будет универ- сальный принцип "разделяй и властвуй". Нужно сделать ряд предположений, проверив каждое из них по очереди. Например, вы можете предположить: "Ошибка должна возни- кать перед вызовом функции xyz". Затем нужно проверить это пред- положение, остановив программу перед вызовом функции xyz и пос- мотрев, есть ли ошибка. Если вы обнаружите ошибку в этой точке, можно сделать следующее предположение, что ошибка возникает в программе где-то раньше. С другой стороны, если при вызове функции xyz все выглядит прекрасно, ваше предположение оказалось неверным. Нужно изменить это предположение на следующее: "Ошибка возникает где-то после вызова функции xyz. Выполнив ряд аналогичных проверок, вы скоро найдете ту часть программы, где возникает ошибка. Это прекрасно, скажете вы, но как же определить после оста- новки программы, что она ведет себя правильно? Один из наилучших путей проверки поведения программы состоит в анализе значений пе- ременных и объектов данных программы. Например, если у вас есть подпрограмма, очищающая массив, вы можете проверить ее работу, остановив программу после выполнения данной подпрограммы и прове- рив затем каждый элемент массива, чтобы убедиться, что он очищен. Стиль отладки ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД У каждого свой стиль как разработки программы, так и ее от- ладки. Те рекомендации по отладке, которые мы здесь приводим, яв- ляются лишь отправными пунктами, которые позволят вам сформиро- вать свой подход. В многих случаях на метод отладки влияет предполагаемое ис- пользование (назначение) программы. Некоторые программы вы пишете для себя, либо они будут использованы только один или два раза для выполнения конкретной задачи. Для таких программ разносторо- нее тестирование всех их элементов было бы напрасной тратой вре- мени, особенно, если после проверки ее выходных данных вы видите, что программа работает правильно. Для тех программ, которые пред- полагается распространять, или для тех, которые выполняют задачу, правильность которой трудно определить с помощью проверки, может оказаться желательным более строгое тестирование. Полное выполнение ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Для простых программ лучший подход, вероятно, состоит в том, чтобы просто запустить программу и посмотреть, что получилось. Если при такой проверке будут обнаружены ошибки, вы можете "сде- лать шаг назад" и запустить программу с максимально простыми входными данными, чтобы проверить затем ее вывод. Затем можно пе- рейти к проверке с более сложными входными данными, и так далее, пока выходная информация не станет неверной. Это даст вам хорошее представление о том, насколько корректно работает программа. Последовательное тестирование ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Если вы хотите полностью убедиться, что программа работает правильно, нужно проверить отдельные подпрограммы, а также убе- диться, что программа выдает ожидаемые результаты для некоторых тестовых входных данных. Это можно сделать двумя способами: можно выполнить проверку каждой подпрограммы, включив ее в програм- му-тест, которая вызывает подпрограмму с тестовыми входными дан- ными, или использовать отладчик для пошагового выполнения каждой подпрограммы, пока не будет выполнена вся программа. Типы ошибок ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Ошибки в программе можно разбить на две больших категории: ошибки, относящиеся к используемому языку (Си, Паскалю или Ас- семблеру), и ошибки, общие для всех языков программирования и операционных сред. По мере отладки программы вы изучите как специфические для языка конструкции, которые могут приводить к неприятностям, так и более общие ошибки программирования, которые вы сделали. Это зна- ние можно использовать в последующем, чтобы постараться избежать повторения таких ошибок. Кроме того, это послужит хорошей базой для того, чтобы быстрее обнаруживать ошибки в следующих програм- мах, которые вы будете писать. Здесь важно понимать, что собой представляет каждая ошибка: относится ли она к общим ошибкам или вызвана непониманием. Это улучшит ваши возможности по разработке кода без ошибок. Кроме то- го, всегда лучше писать программу без ошибок, чем уметь быстро потом их исправлять. Общие ошибки ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД В следующий примерах кратко охватываются различные типы оши- бок, которые могут встречаться в ваших программах. Скрытые эффекты ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Иногда вызов функции может приводить к неожиданным результа- там: char workbuf[20]; strcpy(workbuf, "all done\n"); convert("xyz"); print(workbuf); ... convert(char *p) { strcpy(workbuf, p); while (*p) ... } Здесь правильнее было бы использовать в функции свой собс- твенный рабочий буфер (workbuf). Предположения об инициализации данных ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Иногда вы предполагаете, что другая функция уже установила для вас какие-то значения: char *workbuf; addworkstring(char *s) { strcpy(workbuf, s); } Надежнее будет записать эту подпрограмму, добавив оператор: if (workbuf == 0) workbuf = (char *)malloc(20); Не забывайте об очистке ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Этот тип ошибки может привести к тому, что ваша программа будет долго работать, но в конце-концов исчерпает динамически распределяемую область памяти и аварийно завершит работу: crunch_string(char *p) { char *word = (char*)malloc(strlen(p)); ctrcpy(work,p); ... return(p) } "Забор и столбы" ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Этот тип ошибок аналогичен следующему. Сколько столбов пона- добиться, чтобы построить 100-метровую изгородь,если столбы нужно ставить через каждые 10 метров? Напрашивается ответ 10, но он не- верен, так как в расчет принимается последний столб в конце забо- ра. Приведем простой пример из программирования на Си: for (n = 1; n < 10; n++) { ... /* выполняется только 9 раз */ } Здесь ясно видны числа 1 и 10, и вы можете подумать, что цикл будет выполняться от 1 до 10. Чтобы это действительно было так, нужно вместо < указать <=. Ошибки, специфические для языка Си ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД В "Руководстве пользователя" по Си и С++ есть раздел о не- достатках программирования на Си. Однако лучше всего изучать эту тему во время отладки. Компиляторы Borland Си и Borland C++ прекрасно подходит для того, чтобы находить многие из специфических для языка Си ошибок, о которых другие компиляторы вам даже не сообщают. "Включив" в компиляторе все предупреждающие сообщения, которые он может гене- рировать, вы сэкономите время, необходимое для отладки программы. (О том, как задавать уровень предупреждений, рассказывается в "Руководстве пользователя по Borland C++".) Далее мы приведем далеко не полный перечень возможных ошибок при использовании языка Си. Для некоторых из них Borland Си и Borland C++ генерирует предупреждающие сообщения. Не забудьте найти причину вывода всех предупреждающих сообщений, поскольку они могут быть вызваны возможной допущенной вами ошибкой. Использование неинициализированных локальных переменных ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД В языке Си динамические локальные переменные, описанные внутри функции, будут иметь неопределенное значение, пока вы что- нибудь в них не загрузите. Например: do_ten_times() { int n; while (n < 10) { ... n++; } } Данная функция будет выполнять цикл while неопределенное чис- ло раз, так как перед использованием в качестве счетчика n не инициализируется значением 0. Не следует путать = и == ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД В выражениях язык Си позволяет использовать как присваивание (=), так и проверку на равенство (==). Например: if (x = y) { ... } При этом y будет ошибочно загружено в x, а оператор выпол- нится, если значение y не равно 0. Вероятно, вы предполагали на- писать следующее: if (x == y) ... Не следует путать старшинство операций ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД В языке Си так много операций, что иногда легко спутать, ка- кая из них применяется первой, когда выражение содержит несколько операций. Одна из наиболее общих ошибок состоит в неправильном выполнении комбинации операции сдвига и операции сложения или вы- читания. Например: x = 3 << 1 + 1 Если << указывается перед +, то при вычислении этого опера- тора будет получено значение 12, а не 7, как можно было бы ожи- дать. Неверные арифметические действия с указателями ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Когда вы освоитесь с указателями и будет использовать их для работы с массивами, будьте внимательны при сложении и вычитании указателей. Например: int *intp; intp += sizeof(int); не будет работать так, как вы задумали (предполагая увеличить intp для ссылки на следующий элемент массива). Фактически, intp продвигается на два элемента массива. При сложении или при вычи- тании из указателя Си принимает во внимание размер элемента, на который ссылается указатель, поэтому все, что нужно сделать для продвижения указателя на следующий элемент массива - это опера- ция: intp++ Не забывайте о расширении по знаку ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Будьте аккуратны при присваивании целых чисел различного размера: int i = OXFFFE; long l; l = i; if (l & OX800000000) { ... /* это будет выполнено */ } Один из моментов в Си, который может привести к неприятнос- тям, состоит в том, что вы не знаете о последствиях. Язык Си поз- воляет свободно использовать присваивание одной целочисленной скалярной величины (char, int и т.д.) другой, знак (положительный или отрицательный) сохраняется в переменной большего размера, причем бит знака (старший бит) распространяется на всю старшую часть большего скалярного значения. Например, значение типа int - 2 (Oxfffe) становится значением типа long -2 (oxfffffffe). Помните об усечении ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Данный пример противоположен примеру предыдущего раздела: int i = 1; long l = OX10000; l = i; if (i > 0) { ... /* это не будет выполнено */ } Здесь присваивание i значения 1 приводит к усечению старших 16 бит 1, при этом в i остается значение 0. Использование точки с запятой ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Следующий фрагмент программы на первый взгляд выглядит прек- расно: for (x = 0; x < 10; x++); { ... /* будет выполнено 1 раз */ } Почему фрагмент в фигурных скобках будет выполнен только 1 раз? При ближайшем рассмотрении оказывается, что в конце выраже- ния for содержится точка с запятой (;). Это труднообнаруживаемая ошибка приводит к тому, что цикл выполниться 10 раз, не реализуя никаких действий. Последующий блок выполниться только 1 раз. Это неприятная ошибка, так как ее нельзя обнаружить с помощью обычных методов проверки и идентификации блоков программы. Макрокоманды с побочными эффектами ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Следующая проблема может заставить вас никогда не использо- вать макрокоманды #define: #define toupper(c) 'a'<= (c)&&(c)<='z' ? (c)-'a'-'A': (c) char c, *p; c = toupper(*p++); Здесь p увеличивается 2 или 3 раза, в зависимости от регист- ра символа (строчная или прописная буква). Такую ошибку очень трудно найти, так как побочный эффект скрыт внутри макроопределе- ния. Повторение имен локальных динамических переменных ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Следующую ошибку также трудно обнаружить: myfunc() { int n; for (n=5; n >= 0; n--) { int n = 10; ... if (n == 0) { ... /* никогда не будет выполняться */ } } } } Здесь имя динамической локальной переменной повторно исполь- зуется во внутреннем блоке, скрывая доступ к переменной внешнего блока. При таком повторном использовании имен переменных нужно соблюдать аккуратность. Сделать такую ошибку гораздо легче, чем может вам показаться, так как большинство программистов использу- ют в качестве имени счетчика ограниченный набор имен (i, n и т.д.). Неправильное использование динамических локальных переменных ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Рассмотрим пример функции: int *divide_by_3(int n) { int i; i = n / 3; return(&i); } Смысл данной функции состоит в возврате указателя на резуль- тат. Ошибка состоит в том, что к тому моменту, когда функция возвращает управление, динамическая локальная переменная стано- вится недействительной и будет вероятно заполнена другими данными в стеке. Функция возвращает неопределенное значение ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Если вы не завершаете функцию ключевым словом return, за ко- торым следует выражение, то будет возвращаться неопределенное значение. Например: char *first_capital_letter(char *p) { while (*p) { if ('A' <= *p && *p <= 'Z) return(p); p++; } } Если в строке не содержится буква в верхнем регистре, то возвращается случайное значение ("мусор"). В качестве последней строки данной функции нужно использовать оператор return(0). Неправильное использование ключевого слова break ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Ключевое слово break выполняет выход только из одного уровня операторов do, for, switch или while: for (...) { while (...) { if (...) break; /* хотим выйти из цикла */ } } Здесь break выполняет выход только из цикла while. Это один из немногих случаев, когда предпочтительнее использовать опера- тор goto. Код, не приводящий к результату ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Иногда может встречаться прекрасно компилируемый код, кото- рый не приводит ни к какому результату: a + b; Правильным вариантом этой строки будет: a += b Ошибки, специфические для Паскаля ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Поскольку в Паскале имеются средства, обеспечивающие строгую проверку типов и проверку ошибок, то этот язык мало способствует специфическим для него ошибкам. Однако, поскольку Турбо Паскаль предоставляет вам возможность "выключать" проверку ошибок, вы мо- жете внести ошибки, которые в противном случае не возникли бы. Между тем даже в Паскале есть способы этого избежать. Инициализированные переменные ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Турбо Паскаль не инициализирует переменные автоматически. Вы должны сделать это сами с помощью операторов присваивания или описав такие переменные в виде типизованных констант. Рассмотрим следующую программу: program Test; var I,J,Count : integer; begin for I := 1 to Count do begin J := I*J; Writeln(I:2,' ',J:4) end end Здесь Count будет иметь какое-то случайное значение, содер- жащееся в занимаемой этой переменной ячейке памяти, поэтому вы не сможете определить, сколько раз будет выполнен данный цикл. Кроме того, переменные, описанные внутри процедуры или функции, будут создаваться каждый раз при входе в эту подпрограмму и уничтожать- ся при выходе из нее. Поэтому нельзя полагать, что эти переменные в промежутке между вызовами подпрограммы сохраняют свое значение. Неправильная работа с указателями ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Этот общий тип ошибок встречается при работе с указателями. Во-первых, как уже упоминалось ранее, не следует использовать их до того, как им будет присвоено значение (nil (пустое) или какое- либо другое). Как и все другие переменные или структуры данных, указатель не инициализируется автоматически при его описании. Ему нужно явным образом присвоить начальное значение (передав его в качестве параметра процедуре New или возможно быстрее присвоив ему значение nil). Во-вторых, не ссылайтесь на пустой указатель, то есть не пы- тайтесь обратиться к данным или структуре, на которые он указыва- ет, если указатель имеет значение nil. Например, предположим, что у вас имеется линейный связанный список записей, и вы хотите вы- полнить в нем поиск записи с заданным значением. Ваша программа может выглядеть следующим образом: function FindNode(Head : NodePtr, Val : integer); var Temp : NodePtr; begin Temp := Head; while (Temp^.Key <> Val) and (Tamp <> nil) do Temp := Temp^.Next FindNode := Temp end { FindNode } Если Val не равно полю Key в каком-либо из узлов связанного списка, то эта программа, когда Temp имеет значение nil, будет пытаться вычислить Temp^.Key, что приведет к непредсказуемому по- ведению. Каково же здесь решение? Нужно записать выражение следу- ющим образом: while (Temp <> nil) and (Temp^.Key <> Val) и разрешить вычисление булевских выражений по короткой схеме (с помощью директивы Турбо Паскаля {$B-} или команды OptionsіCompilerіBoolean (ПараметрыіКомпиляторіБулевские выраже- ния)). Таким образом, если Temp не равно nil, второе условие вы- числяться не будет. Наконец, не следует предполагать, что указатель устанавлива- ется в значение nil только потому, что вы передаете его процедуре Dispose или FreeMem. Указатель будет иметь при этом свое исходное значение, однако память, на которую он указывает, будет теперь освобождена, и может использоваться для другой динамической пере- менной После освобождения структуры данных указатель нужно явным образом установить в значение nil. Неправильное использование области действия ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Паскаль позволяет вам использовать большой уровень вложен- ности процедур и функций, и в каждой их этих процедур и функций могут содержаться ее собственные описания. Рассмотрим следующую программу: program Confused; var A,B :integer; procedure Swap(var A,B : integer); var T : integer; begin Writeln('2: A,B,T = ',A:3,B:3,' ',T); T := A; A := B; B := T; Writeln('3: A,B,T =',A:3,B:3,' ',T); end { Swap } begin { тело основной программы Confused } A:= 10; B := 20; T := 3-; Writeln('1: A,B,T = ',A:3,B:3,' ',T); Swap(B,A); Writeln('4: A,B,T = ',A:3,B:3,' ',T); end. { Confused } Выводимая программой информация будет выглядеть примерно следующим образом: 1: A,B,T = 10 20 30 2: A,B,T = 20 10 22161 3: A,B,T = 10 20 20 4: A,B,T = 20 10 30 Все это вызвано тем, что у вас имеется две версии переменных A, B и T. В теле основной программы используются глобальные вер- сии, в процедуре Swap - локальные версии (ее формальные параметры A и B и локальная переменная T). И что еще более запутало ситуа- цию, мы обратились с вызовом Swap(B,A), что означает, что фор- мальный параметр A является на самом деле глобальной переменной B и наоборот. И, конечно, нет никакой связи между локальной и гло- бальной версией переменной T. Настоящей ошибки здесь нет, но проблемы могут возникнуть, когда вы будете считать, что модифицируете что-то, а на самом де- ле это не так. Например, переменная T в теле основной программы не изменяется, хотя вы можете предполагать, что это не так. Этот результат, обратный описанным ранее "скрытым эффектам". Если бы вы использовали следующее описание записи, все стало бы еще более запутанным: type RecType = record A,B : integer; end; var A,B : integer; Rec : RecType; В операторе with ссылка на A или B привела бы к ссылке на fields, а не к ссылке на variables. Неправильное использование точки с запятой ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Как и язык Си, Паскаль допускает использование "пустого" оператора (оператора, состоящего только из точки с запятой). Раз- мещенная в неверном месте точка с запятой может вызвать различные проблемы. Рассмотрим следующую программу: program Test; var I,J : integer; begin for I := 1 to 20 do; begin J := I*I; Writeln(I:2,' ',J:4) end; Writeln('Выполнено!') end. Выводом этой программы будет не список из первых 20 целых чисел и их квадратов, а просто: 20 400 Выполнено! Это вызвано тем, что оператор for I := 1 to 20 заканчивается точкой с запятой. При этом 20 раз будет выполнен пустой оператор. После этого выполняется оператор в блоке begin...end и, наконец, оператор Writeln. Чтобы исправить эту ошибку, нужно просто устра- нить точку с запятой за ключевым словом do. Функция возвращает неопределенное значение ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Когда вы пишете функцию, нужно убедиться, что перед тем, как функция возвращает управление, ее имени присваивается некоторое значение. Рассмотрим следующий пример кода: const NLMax = 100; type NumList = array[1...NLMax] of integer; ... function FindMax(List : Numlist; Count : integer) : integer; var I,MAX : integer; begin Max := List[1]; for I := 2 to Count do if List[I] > Max then begin Max := List[I]; FindMax := Max end end; { FindMax } Эта функция будет прекрасно работать, если максимальным зна- чением в List не является List[1]. В этом случае никогда не будет присвоено значение. Правильный вариант функции должен выглядеть следующим образом: begin Max := List[1]; for I := 2 to Count do if List[I] > Max then Max := List[I]; FindMax := Max end; { FindMax } Уменьшение значения переменных размером в байт или слово ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Будьте внимательны и не уменьшайте беззнаковое скалярное значение (размером в слово или байт) при проверке на >= 0. Следу- ющий фрагмент программы образует бесконечный цикл: var w : word; begin w:= 5; while w >= 0 do w := w - 1; end. После пятой итерации w равно 0. При следующем проходе оно будет уменьшено до значения 65535 (так как переменная размером в слово принимает значения в диапазоне от 0 до 65535), что также >= 0. В этих случаях следует использовать переменные не типа word или byte, а типа integer или longint. Игнорирование границ и особые случаи ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Заметим, что в обеих версиях функции FindMax в предыдущем разделе предполагалось, что Count >= 1. Однако в некоторых случа- ях значение Count может быть равно 0 (то есть список пуст). Если вы в такой ситуации вызовите функцию FindMax, она возвратит то, что оказалось в List[1]. Аналогично, если Count > NLMax, выполне- ние либо завершиться с ошибкой (если разрешена проверка границ), либо поиск максимального значения будет выполняться в ячейках па- мяти, не относящихся к List. Здесь можно предложить два решения. Одно из них состоит, ко- нечно, в том, чтобы никогда не вызывать функцию FindMax, если Count не находится в диапазоне 1..NLMax. Это не пустое замечание. В серьезном программном обеспечении всегда определяются требова- ния, которые нужно выполнять при вызове определенной программы, а затем обеспечивается удовлетворение этих требований при вызове. Другое решение состоит в проверке значения Count и, если оно не находится в диапазоне 1..NLMax, возврате некоторого предопре- деленного значения. Например, вы можете переписать тело функции FindMax следующим образом: begin if (Count < 1) or (Count > NLMax) then Max := -32768 else begin Max := List[1]; for I := 2 to Count do if List[I] > Max then Max := List[I]; end; FindMax := Max end; { FindMax } Однако это приводит к следующему типу ошибок при работе на Паскале - ошибкам диапазона. Ошибки диапазона ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД По умолчанию в Турбо Паскале проверка диапазона выключена. При этом получается более быстрый и компактный код, но в тоже время при этом вы можете следует определенного типа ошибки, та- кие, как присваивание переменным значения, выходящего за их до- пустимый диапазон, или обращение к несуществующему элементу мас- сива (как показано в приведенном выше примере). Первый шаг при обнаружении таких ошибок состоит во включении в программу директивы компилятора {$R+}, которая задает проверку диапазона, компиляции программы и повторном ее запуске. Если вы знаете (или догадываетесь), где содержится ошибка, можно помес- тить указанную директиву перед данной частью программы, а после нее указать директиву {$R-}, разрешив, таким образом, проверку диапазона только в той части программы, где содержится ошибка. Одна из общих ошибок выхода за границы диапазона возникает при использовании для индексации массива цикла while или repeat. Предположим, например, что вы ищете элемент массива, содержащий определенное значение. Вы хотите остановиться после того, как найдете его, или при достижении конца массива. При нахождении элементе вы ходите возвратить его индекс, а в противном случае - 0. Ваш первый вариант может выглядеть так: function FindVal(List : NumList; Count,Val : integer) : integer; var I : integer; begin FindVal := 0; I := 1; while (I <= Count) and (List[I] <> Val) do Inc(I); if I <= Count then FindVal := I end; { FindVal } Это прекрасно, но если Val не содержится в List и вы исполь- зуете обычное вычисление булевских выражений, здесь может возник- нуть ошибка этапа выполнения. Почему? Потому что когда последний раз проверка выполняется в начале цикла while I будет равно Count + 1. Если Count = NLMax, вы выйдете за пределы List. Ошибки, специфические для Ассемблера ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД В каждом языке имеется свое множество ошибок, которые обычно очень легко сделать, но не всегда просто обнаружить. Не является исключением и язык Ассемблера. Мы рассмотрим некоторые типичные ошибки, которые допускаются при программировании на Ассемблере, и дадим рекомендации, как можно их избежать. Программист забывает о возврате в DOS ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД В Паскале, Си и других языках программа завершается и возв- ращается в операционную систему DOS автоматически, когда нет больше выполняемого кода, даже если в программе отсутствует явная команда ее завершения. В языке Ассемблера это не так. Ассемблер выполняет только те действия, которые вы явно указываете. Когда вы запускаете программу, в которой отсутствует команда возврата в DOS, она просто продолжает работать до конца выполняемого кода программы и переходит в код, который находится в примыкающей па- мяти. Программист забывает об инструкции RET ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Заметим, что правильный вызов подпрограммы состоит из вызова подпрограммы из другой части кода, выполнения подпрограммы и возврата из подпрограммы в вызывающую программу. Не забудьте включать в каждую подпрограмму инструкцию RET, по которой управ- ление будет передаваться в вызывающий код. При наборе программы эту директиву легко пропустить. В этом случае ее выполнение за- кончится ошибкой. Генерация неверного типа возврата ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Директива PROC действует двояко. Во-первых, она определяет имя, по которому будет вызываться процедура. Во-вторых, она уп- равляет типом (ближним или дальним) процедуры. Тип процедуры используется Турбо Ассемблером для определения того, какой тип вызовов нужно генерировать при вызове процедуры из того же исходного файла. Тип процедуры также используется для определения типа инструкции RET, которая выполняется, когда про- цедура возвращает управление в вызывающий код. Идея здесь очевидна. Инструкции RET в процедуре должны соот- ветствовать ее типу, не правда ли? И да и нет. Проблема состоит в том, что возможно и часто же- лательно группировать отдельные подпрограммы в единую процедуру; и поскольку эти подпрограммы не имеют соответствующей директивы PROC, их команды RET соответствуют типу общей процедуры, который не обязательно соответствует типу каждой отдельной подпрограммы. Неправильный порядок операндов ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Многие программисты ошибаются и изменяют порядок операндов в инструкциях процессора 8086 на обратный. Это, вероятно, связано с тем, что строка: mov ax,bx которая означает "поместить AX в BX", читается слева направо, и многие создатели микропроцессоров строят соответствующим образом свои ассемблеры. Однако в языке Ассемблера процессора 8086 фирма Intel использовала другой подход, поэтому для нас эта строка оз- начает "поместить BX в AX", что иногда приводит к путанице. Программист забывает о стеке или резервирует маленький стек ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД В большинстве случаев не выделять явно пространство для сте- ка, это все равно, что ходить по тонкому льду. Иногда программы, в которых не выделяется пространство для стека, будут работать, поскольку может оказаться так, что назначенный по умолчанию стек попадет в неиспользуемую область памяти. Но нет никакой гарантии, что такие программы будут работать при любых обстоятельствах, поскольку нет гарантии, что для стека будет доступен по крайней мере один байт. В большинстве программ для резервирования прост- ранства для стека должна присутствовать директива .STACK, и для любой программы эта директива должна резервировать достаточное пространство, чтобы его хватило для максимальных потребностей в программе. ызов подпрограммы, которая портит содержимое нужных регистров ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД При разработке программы на Ассемблере регистры удобно расс- матривать, как локальные переменные, выделенные для использования в процедуре, с которой вы в данный момент работаете. В частности, нередко подразумевают, что при обращении к другим процедурам ре- гистры остаются неизмененными. На самом деле это не всегда так. Регистры - это глобальные переменные, и каждая процедура может сохранить или уничтожить содержимое любого из регистров. Ошибки при использовании условных переходов ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Использование в языке Ассемблера инструкций условных перехо- дов (JE, JNE, JC, JNC, JA, JB, JG и т.д) обеспечивает большую гибкость в программировании, но при этом также очень просто оши- биться, выбрав неверный переход. Кроме того, поскольку в языке Ассемблера анализ условия и переход требуют по крайней меру двух строк исходного кода (а сложных условных переходов нескольких строк), условные переходы в языке Ассемблера менее очевидны и больше способствуют ошибкам, чем соответствующие операторы Паска- ля и Си. Неверное понимание работы префикса REP ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Команды обработки строк имеют одну необычную особенность: после их выполнения используемые ими указатели сдвигаются таким образом, что указывают на адрес, отличающийся на 1 байт (или 2 байта, если если длина команды равна одному слову) от последнего обработанного адреса. Это может привести к некоторой путанице при повторении команд обработки строк, особенно команд REP SCAS и REP CMPS. Нулевое содержимое CX и работа с целым сегментом ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Повторное выполнении любых команд обработки строк при ра- венстве нулю регистра CX не даст никакого результата. Это может быть удобно в том смысле, что нет необходимости проверять его на ноль перед повторным выполнением команд обработки строк. С другой стороны, невозможно получить доступ к каждому байту в сегменте с помощью байтовых команд обработки строк. Неправильная установка флага направления ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД При выполнении команды обработки строк связанные с ней ука- затели (SI, DI или оба) получают положительное или отрицательное приращение. Это зависит от состояния флага направления. С помощью команды CLD флаг направления может быть сброшен в 0. В этом случае при выполнении команд обработки строк указатель получает положительное приращение (смещается в сторону старших адресов). С помощью команды STD флаг направления устанавливается в 1. В этом случае указатель получает отрицательное приращение (сдвигается в сторону младших адресов). После того, как флаг нап- равления был установлен в определенное состояние, он будет оста- ваться в нем до тех пор, пока не будет выполнена еще одна команда CLD или STD, или пока значения флагов не будут извлечены из стека с помощью команды POPF или IRET. С одной стороны, удобно иметь возможность устанавливать флаг направления в определенное состоя- ние только один раз, а затем выполнять серию команд, которые должны использовать заданное направление. С другой стороны, это может привести к появлению неустойчивых и труднообнаруживаемых ошибок, в результате которых команды обработки строк работают по- разному в зависимости от работы команд, которые были выполнены значительно раньше. Ошибки при повторении команд сравнения строк ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Команда CMPS сравнивает содержимое двух областей памяти, а команда SCAS сравнивает содержимое накапливающего регистра с со- держимым области памяти. Когда перед одной из этих команд стоит префикс REPE, она выполняет сравнение, либо пока CX не становится равным нулю, либо пока не обнаружится, что операнды не равны. Когда перед командой стоит префикс REPNE, она выполняет сравне- ние, либо пока CX не становится равным нулю, либо пока не обнару- жится что операнды равны. К несчастью, легко перепутать, где ка- кой префикс нужно использовать. Ошибки при назначении сегмента строк ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Все строковые команды по умолчанию используют в качестве сегмента исходных данных (если он есть) сегмент DS, а в качестве сегмента результирующих данных (если он есть) сегмент ES. Легко забыть об этом и попытаться, скажем, выполнить команду STOSB над сегментом данных, поскольку все данные, обрабатываемые не строко- выми командами, обычно находятся именно в этом сегменте. Неправильное преобразование из байта в слово ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД В общем случае, для команд обработки строк желательно ис- пользовать максимально возможный размер данных (обычно слово, а для процессора 80386 - двойное слово), поскольку с данными боль- шего размера эти команды обычно работают быстрее. Однако здесь имеются две ловушки. Во-первых, преобразование из количества байт в количество слов с помощью простой команды: shr cx,l приведет к потере байта, если CX имеет нечетное значение, пос- кольку младший значащий бит будет сдвинут за пределы слова. Во-вторых, следует помнить, что команда SHR делит количество байт на два. Использование, скажем, команды STOSW с количеством байт, а не слов, может уничтожить другие данные и вызвать самые разнообразные ошибки. Использование нескольких префиксов ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Команды обработки строк с несколькими префиксами работают ненадежно, и их следует по возможности избегать. Необязательные операнды в командах обработки строк ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Необязательные операнды в командах обработки строк использу- ются только для задания размера данных и изменения сегмента и не гарантируют фактический доступ к данной области памяти. Уничтожение содержимого регистра при умножении ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Умножение (8 на 8 бит, 16 на 16 бит, либо 32 на 32 бита) всегда уничтожает содержимое как минимум одного регистра, не яв- ляющегося накапливающим регистром, который используется в качест- ве исходного операнда. Ошибки, связанные с изменением содержимого регистров ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Команды обработки строк, такие как MOVS, STOS, LODS, CMPS и SCAS, могут влиять на состояние некоторых флагов и содержимое трех регистров при выполнении единственной команды. При использо- вании команд обработки строк следует помнить, что содержимое од- ного из регистров SI или DI (или обоих сразу) получает положи- тельное или отрицательное приращение (в зависимости от состояния флага направления) при каждом выполнении команды обработки строк. Содержимое регистра CX также получает отрицательное приращение как минимум один раз и, возможно, уменьшается до нуля при каждом использовании команды обработки строк с префиксом REP. Изменение состояния флага переноса ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД В то время как одни команды неожиданно для программиста вли- яют на состояние регистров и флагов, другие команды не влияют да- же на те флаги, состояние которых было бы желательно изменить. Программист долго не использует флаги ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Состояние флагов сохраняется до тех пор, пока не будет вы- полнена следующая команда, которая его изменяет, что обычно про- исходит достаточно быстро. Поэтому рекомендуется после установки флагов выполнять действия над ними как можно быстрее, чтобы избе- жать самых разнообразных ошибок, связанных с неверной установкой флагов. Смешение операндов в памяти и непосредственных операндов ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Программа на языке Ассемблера может обращаться либо к смеще- нию области памяти, в которой хранится переменная, либо к значе- нию этой переменной. К сожалению, в языке Ассемблера нет ни инту- итивных, ни строгих способов, позволяющих различить эти два вида обращений, и в результате программисты часто путают обращения к смещению и обращения к значению. Ошибки, связанные с возвратом в начало сегмента ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Один из самых сложных моментов в программировании для мик- ропроцессора 8086 состоит в том, что к памяти нельзя обращаться как к одному большому массиву байт. Вместо этого память делится на части (сегменты) размером 64К (килобайт), и доступ к ним осу- ществляется через сегментные регистры. Сегментация памяти может вызвать труднообнаруживаемые ошибки, поскольку если программа пы- тается обратиться к адресу, который находится за границами сег- мента, в действительности вместо этого происходит возврат в нача- ло того же сегмента. Сохранение содержимого регистров при обработке прерываний ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Каждый обработчик прерываний должен обязательно сохранять содержимое всех регистров. Хотя и допускается сохранять содержи- мое только тех регистров, которое изменяется данным обработчиком прерываний, для надежности работы все же рекомендуется заносить содержимое всех регистров в стек при входе в обработчик прерыва- ний и извлекать его из стека при выходе. Игнорирование групп в таблицах операндов и данных ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Использование сегментных групп позволяет программисту логи- чески разбивать данные на несколько областей, исключая при этом необходимость загружать сегментный регистр каждый раз, когда не- обходимо перейти от одной из таких логических областей данных к другой. К сожалению, тот способ, который используется для обработки сегментных групп в макроассемблере фирмы Microsoft (MASM), может вызвать некоторые проблемы, и пока не появился язык Турбо Ассемб- лер, сегментные группы доставляли программистам много неприятнос- тей. И хотя этих неприятностей практически невозможно было избе- жать, сегментные группы были нужны для связи ассемблерного кода с языками высокого уровня, такими как Си. В режиме Quirks языка MASM Турбо Ассемблер эмулирует MASM, и это означает, что в этом режиме он имеет те же проблемы, что и MASM.Если вы не собираетесь использовать режим Quirks языка MASM, можете больше ничего о нем не читать, однако если вы планируете работать с этим режимом, вам следует обратиться за дополнительной информацией к "Руководству пользователя по Турбо Ассемблеру". Проверка ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Создание программы с допустимыми входными данными составляет только часть функций проверки. В следующих разделах обсуждаются некоторые важные случаи проверки, которым должны подвергаться каждая программа, прежде чем можно будет сделать вывод о ее пра- вильной работе. Проверка граничных условий и случаи ограничения ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Если вы считаете, что подпрограмма должна работать с данны- ми, принимающими значение в определенном диапазоне, вы должны подвергнуть эту подпрограмму проверке с данными, принимающим раз- личные значение в этом диапазоне. Например, если в вас имеется подпрограмма, выводящая на экран список длиной от 1 до 20 элемен- тов, вы должны убедиться, что она ведет себя правильно и в том случае, когда в списке имеется ровно 1 элемент, и в том случае, когда в списке 20 элементов (здесь могут скрываться различные ошибки, в частности, ошибка типа "столбы и забор", описанная ра- нее). Ввод ошибочных данных ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Когда вы убедитесь, что программа работает во всем диапазоне допустимых данных, следует убедиться, что она ведет себя коррект- но, когда вы задаете недопустимые входные данные. Например, убе- дившись, что предыдущая программа воспринимает значения в диапа- зоне от 1 до 20, нужно также убедиться, что 0 или 21 значение ей отвергаются. Отсутствие входных данных ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Этот момент при проверке и создании программы часто упуска- ют. Если вы пишете программу, которая правильно себя ведет при отсутствии входных данных, работа с ней значительно упростится. Отладка, как часть процесса создание программы ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Когда вы начинаете разработку программы, можно заранее зап- ланировать этап отладки. Необходимо установить, в какой степени различные части вашей программы должны выполнять проверку на до- пустимые входные и выходные данные. При большом объеме проверок вы получите в результате очень гибкую программу, которая часто будет сообщать вам об ошибочной ситуации, но продолжать работать после выполнения некоторых дейс- твий по восстановлению. Однако при этом объем программы возрастет и работать она будет медленнее. Такой тип программ довольно легко отлаживать, поскольку до возникновения опасной ситуации подпрог- раммы сами сообщают вам о недопустимых входных данных. Можно также реализовать программу, в которой выполняется ма- ло проверок на допустимость входных и выходных данных или такие проверки совсем отсутствуют. Такая программа будет меньшей по объему и будет быстрее выполняться, но неверные входные данные или маленькая ошибка могут привести к аварийному завершению ее работы. Такой тип программ обычно труднее всего отлаживать, так как небольшая ошибка может проявиться при выполнении намного позднее. Это затрудняет выявление того места, где содержится ошибка. Большинство создаваемых программ сочетают в себе оба этих метода. Данные, воспринимаемые из внешних источников (например, вводимые пользователем или считываемые из файла на диске) подвер- гаются обычно более тщательной проверке, чем данные, передаваемые при вызове от одной подпрограммы к другой. Пример сеанса отладки ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД В примере сеанса отладки используются те методы, о которых мы рассказывали в предыдущих разделах. Отлаживаемая программа представляет собой вариант демонстрационной программы, использо- ванной в Главе 3 (BCDEMO.C или TPDEMO.PAS), только в нее предна- меренно внесены некоторые ошибки. Убедитесь, что в вашем текущем каталоге содержатся два фай- ла, необходимые для демонстрации отладки. Если вы отлаживаете программу на Турбо Паскале, вам понадобятся файлы TPDEMOB.PAS и TPDEMOB.EXE. Если вы работаете на языке Си, вам потребуются файлы BCDEMOB.C и BCDEMOB.EXE. (Буква B в конце имен файлов, означает, что в эту версию внесена ошибка.) Сеанс отладки программы на языке Си ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД В данном разделе в качестве примера используется программа на языке Си. Если вы программируете на Паскале, см. ниже пример сеанса отладки с использованием программы Турбо Паскаля. Поиск ошибок ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД До того, как начать сеанс отладки, давайте запустим демонс- трационную программу с ошибкой и посмотрим, что она делает непра- вильно. Для запуска программы наберите: BCDEMOB Вам выведется подсказка для ввода строк текста. Введите две строки текста: one two three four five six Последняя пустая строка завершает ваш ввод. После этого программа BCDEMOB выводит результаты анализа введенных вами строк: Arguments: (1) Enter a line (empty line to end): one two three (2) Enter a line (empty line to end): fou five six Enter a line (empty line to end): Total number of letters = 7 (3) Total number of lines = 6 (4) Total word count = 2 (5) Average number of words per line = 0.3333333 (6) 'E' orrurs 1 times, 0 times at start of a word (7) 'F' occurs 1 times, 1 times at start of a word 'N' occurs 1 times, 0 times at start of a word 'O' occurs 2 times, 1 times at start of a word 'R' occurs 1 times, 0 times at start of a word 'U' occurs 1 times, 0 times at start of a word There is one word 3 characters long (8) There is one word 4 characters long (9) 1 - аргументы; 2 - введите строку (пустая строка завершает ввод); 3 - общее число букв; 4 - общее число строк; 5 - общее число слов; 6 - среднее число слов на строке; 7 - 'E' встречается 1 раз, 0 раз в начале слова; 8 - имеется одно слово длиной в три символа; 9 - имеется одно слово длиной в 4 символа. Заметим, что в общем числе слов и букв имеется ошибка. Позд- нее окажется, что таблицы частот букв и слов основываются на оши- бочном значении счетчика букв и слов. Такая ситуация, когда в программе сразу несколько неверных мест, довольно типична. Это часто встречается на начальном этапе отладки. Разработка плана действий ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Первая задача состоит в том, чтобы решить, с какой ошибкой нужно разобраться в первую очередь. Хорошим правилом здесь явля- ется следующее правило: начинайте с ошибки, которая произошла "первой". В данной программе каждая вводимая строка разбивается на слова, после чего анализируется, наконец, когда будут введены все строки выводятся таблицы. Так как счетчики букв и слов, как и таблицы, неверны, можно предположить, что что-то делается неверно при начальной разбивке и подсчете. Теперь, после того, как вы немного обдумали проблему и наме- тили в общих чертах пути решения, пора начать отладку. Здесь стратегия будет состоять в том, чтобы проверить подпрограмму makeintowords и посмотреть, правильно ли она разбивает строку на завершающиеся нулевым символом слова, а затем посмотреть, пра- вильно ли подпрограмма analyzewords выполняет подсчет для анали- зируемой строки. Запуск Турбо отладчика ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Чтобы запустить пример сеанса отладки, наберите: TD BCDEMOB При этом Турбо отладчик загрузит демонстрационную программу, в которой содержится ошибка, и выведет начальный экран. Если вы хотите выйти из сеанса отладки и вернуться в DOS, нажмите клавиши Alt-X (это можно сделать в любой момент). Если вы безнадежно "заблудились", можно в любое время перезагрузить демонстрационную программу, нажав клавиши Ctrl-F2, и начать сначала (при этом точ- ки останова и выражения просмотра очищены не будут). Поскольку первое, что нам нужно сделать - это проверка пра- вильности работы подпрограммы makeintowords, нужно выполнить программу до выполнения этой подпрограммы, а затем проверить все, что требуется. В этом случае можно использовать два подхода: вы можете выполнить шаг программы, выполнив makeintowords и убедив- шись,что она делает все правильно, или можно остановить программу после выполнения подпрограммы makeintowords, и проверить ее ре- зультаты. Убедиться в правильности работы подпрограммы makeintowords довольно просто. Для этого можно проверить формируемый ей выход- ной буфер. Давайте выберем второй подход. Чтобы сделать это, пе- реместите курсор на строку 42 и нажмите клавишу F4, выполнив программу до этой строки. Появится экран программы, после чего вам нужно ввести: one two three и нажать клавишу Enter. Проверка ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Итак, вы остановились на строке исходного кода после вызова функции makeintowords. Взгляните на содержимое буфера и посмотри- те, все ли правильно. Переместите курсор на одну строку вверх и поместите его под словом buffer. После этого нажмите Alt-F10 (для вывода окна Inspector (Проверка)). В окне проверки выведется со- держимое буфера buffer. Для просмотре элементов массива исполь- зуйте клавиши стрелок. Обратите внимание, что подпрограмма makeintowords действительно поместила в конце каждого слова нуле- вой символ (0). Это означает, что вам нужно просмотреть другую часть программы и проверить, правильно ли работает подпрограмма analyzewords. Для этого сначала удалите окно проверки, нажав кла- вишу Esc. Затем дважды нажмите клавишу F7 для выполнения програм- мы до начала работы analyzewords. Проверьте, что analyzewords была вызвана с корректным указа- телем в буфере. Для этого переместите курсор под bufp и нажмите Alt-F10 I. Вы увидите, что bufp действительно указывает на завер- шающуюся нулевым символом строку 'one'. Для удаления окна про- верки нажмите клавишу Esc. Поскольку ошибка возникает, очевидно, при подсчете символов и слов, давайте поместим точку останова в то место, где подсчитываются слова и символы. 1. Переместите курсор на строку 93 и нажмите клавишу F2, чтобы установить точку останова. 2. Переместитесь на строку 97 и установите другую точку ос- танова. 2. Наконец, установите точку останова на строке 99, благода- ря чему вы сможете увидеть значение счетчика символов, возвращаемое данной функцией. Задание нескольких точек останова (как в данном примере) - это типичный способ, позволяющий узнать, все ли в программе дела- ется правильно, и проверить значения важных данных при каждом ос- танове программы на очередной точке останова. Окно Watch ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Запустите программу, нажав клавишу F9. Программа остановит- ся, когда она достигнет точки останова на строке 93. Теперь можно просмотреть значение charcount (счетчик символов). Так как вы хо- тите проверять его каждый раз, когда встречается данная точка ос- танова, удобно использовать команду Watch (Просмотр), чтобы по- местить charcount в окно Watches (Просмотр). Переместите курсор под wordcounts (счетчик слов) и нажмите Alt-F10 W. В окне прос- мотра в нижней части экрана выводится текущее значение 0. Чтобы убедиться, что символ подсчитывается правильно, выполните одну строку, нажав клавишу F7. В окне просмотра (Watches) действитель- но выводится, что значение charcount = 1. Диалоговое окно Evaluate/Modify ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Снова запустите программу, нажав клавишу F9. Теперь вы вер- нулись на строку 93 для обработки другого символа. Снова дважды нажмите F9 для считывания последней буквы слова и завершающего нуля. Теперь charcount показывает корректное значение 3, а массив wordcounts будет обновлен для подсчета слов. Далее все отлично. Нажмите снова F9, чтобы начать обработку следующего слова в буфе- ре. Ага! Что-то не так. Вы ожидаете, что программа остановится снова на строке 93 (на точке останова) для обработки другого символа. Но она этого не делает. Она выполняется дальше и возвращается из функции. Единственным путем оказаться на строке 99 является истинное зна- чение проверяемого в цикле while значения. Это означает, что *bufp != 0 должно при вычислении получать ложное значение (false). Чтобы проверить это, переместитесь к строке 83 и отметьте все выражение *bufp != 0, поместив курсор под *, нажав клавишу Ins, и переместив курсор на завершающий 0 перед ). Теперь вычис- лите это выражение, открыв диалоговое окно DataіEvaluate/Modify (ДанныеіВычисление/Модификация) и нажав клавишу Enter, а затем выбрав переключатель Eval (Вычисление), чтобы отмеченное выраже- ние было воспринято. Значение в самом деле равно 0. Нажмите дваж- ды клавишу Esc для возврата в окно Module (Модуль). Эврика! ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Теперь, чтобы обнаружить ошибку, нужно сделать некий анали- тический скачок. Причина того, что bufp указывает на 0, состоит в том, что внутренний цикл while, начинающийся на строке 86, остав- ляет его в конце слова. Для продолжения на следующем слове вы должны увеличить bufp и переместить его с 0, который завершал предыдущее слово. Для этого перед строкой 97 нужно добавить опе- ратор "bufp++". Вы можете перекомпилировать свою программу с этим оператором, однако Турбо отладчик позволяет вам "вставлять" выра- жения, используя для этого особый вид точек останова. Для этого нужно сначала перезагрузить программу, нажав кла- виши Ctrl-F2 (после этого вы можете начать проверку, сбросив сос- тояние программы). Теперь удалите все точки останова, которые бы- ли вами установлены в предыдущем сеансе отладки, для чего нажмите клавиши Alt-B D. Вернитесь к строке 97 и снова установите точку останова, нажав клавишу F2. Теперь откройте окно Breakpoints (Точки останова), нажав клавиши ALt-V B. Установите эту точку ос- танова, чтобы выполнять выражение bufp++ каждый раз, когда оно встречается. Для этого сделайте следующее: 1. Выберите команду ViewіBreakpoint (ОбзоріТочка останова). 2. Откройте окно Breakpoints (Точки останова), нажав клавиши Alt-F10. 3. Выберите команду Set Option (Установить параметры) для открытия диалогового окна Breakpoint Options (Параметры точки останова. 4. Установите селективный переключатель Action (Действие) в значение Execute (Выполнение). 5. Для вывода подсказки Action Expression (Выражение дейс- твия) нажмите клавишу Tab. 6. Введите bufp++ в ответ на подсказку. 7. Нажмите клавишу Esc, чтобы закрыть диалоговое окно, и клавиши Alt-F3 для возврата в окно Module (Модуль). Теперь запустите программу, нажав клавишу F9. Введите две входных строки: one two three four five six В ответ на третью подсказку нажмите клавишу Enter, а когда программа завершит работу, нажмите клавиши Alt-F5, чтобы увидеть ее экран (экран пользователя). Вы можете заметить, что ситуация существенно улучшилась. Об- щее число строк и слов выглядит неверным, но таблица правильна. Остановитесь на начале подпрограммы printstatistics и посмотрите, передается ли ей для вывода корректное значение. Для этого снача- ла перезагрузите программу (чтобы начать проверку заново), нажав клавиши Ctrl-F2. Затем перейдите к строке 104 и нажмите клавишу F4, чтобы выполнить программу до этой строки. Переместите курсор на аргумент nlines и нажмите Alt-F10 I, чтобы посмотреть на его значение. Вы видите значение 6, хотя должно быть значение 2. Теперь вернитесь назад, туда, где эта подпрограмма вызыва- лась из основной программы, и посмотрите на значение nlines (чис- ло строк) там. Переместите курсор на строку 36 и поместите его под nlines. Нажмите клавиши Alt-F10 I для вывода его значения. В основной программе значение nlines равно 2, а это правильно. Если вы перейдете в них к строке 46, то увидите, что два аргумента - nwords и nlines - переставлены местами. Компилятор здесь не может определить, какой именно порядок вы имели в виду. Он использует то, что указано. Если вы исправите эти две ошибки, программа будет работать правильно. Если вы достаточно любопытны, то можете попробовать запустить исправленную версию программы BCDEMO.EXE. Сеанс отладки с использованием программы на Паскале ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Остальная часть данной главы посвящена описанию примера се- анса отладки программы, написанной на Турбо Паскале. Если вы ра- ботаете с Borland C++, то просмотрите предыдущие разделы, в кото- рых описывается сеанс отладки программы на языке Си. Поиск ошибок ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Перед началом сеанса отладки давайте запустим демонстрацион- ную программу на Паскале, в которой содержится ошибка, и посмот- рим, что она делает неверно. Скомпилированная версия этой прог- раммы уже содержится на дистрибутивном диске. Для запуска программы наберите ее имя и передайте ей в ко- мандной строке три аргумента: TPDEMOB first second third Вам будет выведена подсказка для ввода строк текста. Введите две строки текста следующим образом: ABC DEF GHI abc def ghi Ввод завершает последняя пустая строка. После этого TPDEMOB выводит анализ введенного текста: 9 letter(s) in 3 words in 2 lines(s) (1) Average of 0.67 words per line (2) Word length: 1 2 3 4 5 6 7 8 9 10 (3) Frequency: 0 0 3 0 0 0 0 0 0 0 (4) Letter: M (5) Frequency: 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 (6) Word starts: 1 0 0 1 0 0 1 0 0 0 0 0 0 0 0 (7) Letter: Z Frequency: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Word starts: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Program name: C:\td\tpdemob.exe (8) Command line parameters: firs# secon% third (9) 1 - 9 букв в 3 словах; 2 - в среднем 0.67 слов на строке; 3 - длина слова; 4 - частота; 5 - буква; 6 - частота; 7 - начинает слово; 8 - имя программы; 9 - параметры командной строки. В этой выходной информации содержится пять различных ошибок: 1. Число слов сообщается неверно (3 вместо 6). 2. Число слов на строку неверно (0.67 вместо 3). 3. В заголовках второй и третьей таблиц выводится только по одной букве (вместо A..M, N..Z). 4. Вы ввели две строки, каждая из которых содержит буквы от A до I, но в таблицах частоты букв показан только счетчик со значение 1 для этих букв. 5. Последний символ каждого параметра командной строки был потерян, и на экран выводится случайный символ (хотя па- раметры введены правильно). Выбор стратегии поиска ошибок ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Первая задача состоит в том, чтобы решить с какой из ошибок разбираться в первую очередь. Здесь можно предложить хорошее пра- вило: начинайте с той ошибки, которая появилась первой. В данной программе, после того, как данные инициализируются процедурой Init, ввод с клавиатуры считывается функцией GetLine, а затем об- рабатывается процедурой ProcessLine, пока пользователь не введет пустую строку. ProcessLine просматривает каждую строку ввода и обновляет глобальные счетчики. После этого процедурой ShowResults выводятся результаты. Наконец, полностью независимая подпрограм- ма - процедура ParmsOnHeap - строит связанный список параметров командной строки в динамически распределяемой области памяти, а затем выводит этот список в конце программы. Среднее число слов на строку вычисляется процедурой ShowResults на основе числа строки и слов. Так как значение счет- чика неверно, очевидно стоит взглянуть на процедуру ProcessLine и посмотреть, как изменяется значение переменной NumWords (число слов). Даже если значение NumWords верно, число 0.67 слов на строку не имеет смысла. Тогда ошибка возможно содержится в вычис- лениях процедуры ShowResults, на что также стоит обратить внима- ние. Заголовки для всех таблиц выводятся в результате обращения к процедуре ShowResults. Перед отслеживанием второй и третьей ошиб- ки следует подождать завершения работы основного цикла. Так как счетчики слов и букв содержат неверные значения, вероятно что-то упущено в процедуре ProcessLine (это относиться к первой и чет- вертой ошибке). Наконец, когда вы закончите исследовать части программы, от- носящиеся к работе со счетчиками слов и букв, для поиска и исп- равления последней (пятой) ошибки займитесь процедурой ParmsOnHeap. Теперь, после того, как обдумали проблему и наметили план ее решения, пришло время непосредственно начать отладку. Запуск Турбо отладчика ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Для того, чтобы начать отладку нашего примера, загрузите от- ладчик и укажите те же параметры командной строки: TD TPDEMOB first second third Турбо отладчик загрузит версию демонстрационной программы, содержащую ошибку, и выведет начальный экран, меню и т.д. Если вы хотите выйти из сеанса отладки и вернуться в DOS, нажмите клавиши Alt-X (это можно сделать в любой момент). Если вы безнадежно "заблудились", можно в любое время перезагрузить демонстрационную программу, нажав клавиши Ctrl-F2, и начать сначала (при этом точ- ки останова и выражения просмотра очищены не будут). Для отладки таких подпрограмм, как ProcessLine, можно пред- ложить два подхода. Вы можете либо выполнять ее построчно (по ша- гам), убедившись, что она все делает правильно, либо остановить программу непосредственно после выполнения процедуры ProcessLine и посмотреть, верны ли результаты. Так как оба счетчика содержат неверные значения, лучше внимательно проанализировать процедуру ProcessLine и посмотреть, как обрабатываются символы. Перемещение по программе ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Итак, вы собираетесь запустить программу и исследовать про- цедуру ProcessLine. Сделать это можно несколькими способами. Мож- но нажать четыре раза клавишу F8 (для пропуска вызовов процедур и функций), затем нажать один раз F7 (для трассировки вызова ProcessLine). Можно переместить курсор на строку 231, нажать F4 (команда Go to Cursor - Выполнение до курсора), а затем нажать один раз F7 для того, чтобы начать выполнение процедуры ProcessLine (трассировка вглубь). Можно привести и другие способы, однако используем следую- щий. Нажмите клавиши Alt-F9. При этом вам выведется подсказка (диалоговое окно) для ввода адреса кода, до которого вы хотите выполнить программу. Наберите ProcessLine и нажмите клавишу Enter. Программа будет выполнена до того места, когда управление получает процедура ProcessLine. Когда вам выведется подсказка для ввода строки, введите те же данные, что и раньше (то есть, ABC DEF GHI). Здесь есть несколько циклов. Во внешнем цикле просматривает- ся вся строка. Внутри данного цикла имеется цикл для пропуска символов, отличных от букв, а второй цикл обрабатывает слова и буквы. Переместите курсор к циклу while на строке 133 и нажмите клавишу F4 (Выполнение до курсора). Данный цикл будет выполняться, пока он не достигнет конца строки, или не будет найдена буква. Последнее условие проверяется с помощью вызова булевской функции IsLetter. Для трассировки функции IsLetter нажмите клавишу F7. IsLetter представляет собой вложенную функцию, которая воспринимает значение символа и возв- ращает значение True (истинное значение), если это буква, и зна- чение False в противном случае. При поверхностном анализе оказы- вается, что она проверяет только прописные буквы (верхний регистр). А она должна проверять символы в диапазоне 'A'...'Z' и 'a'...'z' или перед выполнением проверки преобразовывать символы в верхний регистр. Еще один ключ к поиску ошибки дает анализ обеих введенных строк. Вы ввели буквы верхнего и нижнего регистра от 'A' до 'I', но в общем итоге выведена только половина букв. Теперь вы уже знаете, почему. Давайте вернемся назад к строке, в которой вызывается IsLetter, с помощью еще одного метода перемещения: нажмите клави- ши Alt-F8, по которым программа будет выполнена до последнего оператора процедуры или функции. Так как вторая введенная строка содержит только буквы нижнего регистра, каждый символ обрабатыва- ется, как пробел, и пропускается. Это приводит к неверному значе- нию счетчиков слов и букв и выявляет причину ошибок 1 и 4. Диалоговое окно Evaluate/Modify ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Кстати, существует еще один прекрасный способ выявить непра- вильное поведение IsLetter. Нажав клавиши Alt-D E, выведите диа- логовое окно Evaluate/Modify (Вычисление/Модификация) и введите следующее выражение: IsLetter('a') = IsLetter('A') И тот, и другой параметр (a и A) являются буквами, но ре- зультат вычисления False подтверждает, то они интерпретируются функцией IsLetter по-разному. (Окна вычисления и просмотра можно использовать для вычисления выражений, выполнения присваиваний, или, как в данном случае, вызовов процедур и функций. Более под- робно об этом рассказывается в Главе 6.) Проверка ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Итак, две ошибки выявлены, остались три. Ошибку 2 гораздо проще найти, чем предыдущие. Нажмите Alt-F8 для вызова из ProcessLine, затем переместите курсор к строке 234 и нажмите кла- вишу F4, чтобы выполнить программу до этой позиции курсора. Программа TPDEMOB выведет вам подсказку для ввода строки. Наберите abc def ghi и нажмите Enter. В ответ на повторный вывод подсказки просто нажмите клавишу Enter. Теперь нажмите клавишу F7 для трассировки процедуры ShowResults. Вспомните, что вы хотите определить, почему среднее число слов в строке имеет некорректное значение. В первой строке ShowResults вычисляется число строк на слово, а не число слов на строке. Ясно, что этот порядок следует изменить на обратный. Поскольку вы уже находитесь в данном месте, можно убедиться, что NumLines (число строк) и NumWords (число слов) имеют те зна- чения, которые вы ожидаете. NumLines должно быть равно 2 и, пос- кольку вы нашли ошибку в IsLetter, но не исправили ее, NumWords должно быть равно 3. Переместите курсор к NumLines и нажмите Alt- F10 I для проверки значения переменной. Окно Inspector (Проверка) показывает, что значение NumLines действительно равно 2. Теперь вы можете проанализировать NumWords. Нажмите клавишу Esc, чтобы закрыть окно Inspector, затем переместите курсор дальше на NumWords и снова нажмите Alt-F10 I (можно использовать также сок- ращение - клавиши Ctrl-I). NumWords содержит ожидаемое некоррект- ное значение 3, поэтому можно следовать дальше. Однако стоит ли торопиться? В этих вычислениях есть еще одна ошибка, отсутствующая в нашем списке. Перед выполнением деления значение второй переменной не проверяется на 0. Если вы запустите программу сначала и совсем не введете данные (нажав от ответ на подсказку Enter), то программа аварийно завершит работу (даже ес- ли вы поменяете местами делимое и делитель). Чтобы убедиться в этом, нажмите Esc, чтобы закрыть окно Inspector, затем нажмите клавиши Alt-R P, чтобы завершить текущий сеанс отладки и F9, чтобы запустить программу сначала. В ответ на подсказку программу TPDEMOB нажмите клавишу Enter. Программа за- вершит работу и на экран выведется окно ошибки. Оператор следует изменить следующим образом: if NumLines <> 0 then AvgWords := NumWords / NumLines else AvgWords := 0; С ошибкой 2 покончено. Поскольку вы работаете с окном Inspector (Проверка), попробуйте использовать его для просмотра структуры данных. Переместите курсор выше к описанию LetterTable на строке 50. Поместите курсор на слово LetterTable и нажмите клавиши Alt-F10 I. Вы увидите, что это массив записей длиной в 26 элементов. Для просмотра каждого элемента массива используйте клавиши перемещения курсора, а для углубления в элемент массива - клавишу Enter. Это очень мощный способ проверки структур данных, он будет особенно удобен для последующего исследования связанного списка в процедуре HeapOnParms. Выражения просмотра ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Теперь давайте исследуем ошибку 3 в процедуре ShowResults (в выводе заголовка таблиц). Поскольку вы уже завершили программу, исследуя ошибку деления на 0, подготовьте ее для другого сеанса, нажав клавиши Alt-P (сброс программы). Затем нажмите клавиши Alt- F9, наберите showresults и нажмите Enter. После этого введите уже знакомые вам данные ABC DEF GHI и нажмите клавишу Enter. Наконец, наберите abc def ghi и дважды нажмите Enter. Теперь нужно остано- вить Турбо отладчик на ShowResults. В ShowResults для вывода таблиц букв используется вложенная процедура ShowLetterInfo Переметите курсор на строку 103, нажмите клавишу F4, затем F7 для перехода в ShowLetterInfo. Здесь имеется три цикла for. В первом цикле выводится заго- ловок таблицы, а во втором и третьем - значения частот. Исполь- зуйте клавишу F7 для перехода в первый цикл на строке 63. Позици- онируйте курсор на переменных FromLet и ToLet и используйте кла- виши Alt-F10 I для проверки их значений. Они выглядят верными (первое равно 'A', а второе - 'M'). Нажмите клавиши Alt-F5 для вывода экрана пользователя. Для возврата к окно Module (Модуль) используйте любую клавишу. При выполнении подобного цикла очень удобно использовать ок- но Watch (Просмотр). Позиционируйте курсор на ch и нажмите клавиши Ctrl-W. Теперь для выполнения цикла по шагам используйте клавишу F7. Как и ожидалось, мы переходим к оператору Write на строке 64. Однако, если вы посмотрите на окно Watch (Просмотр), то увидите, что значение ch уже равно 'M' (уже выполнен весь цикл!). После ключевого слова do имеется лишняя точка с запятой, поэтому данный цикл 13 раз выполняется вхолостую. Когда управле- ние переходит к оператору Write на строке 64, то выводится теку- щее значение ch ('M'). Устранение лишней точки с запятой позволя- ет избавится от ошибки 3. Следующая ошибка ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Теперь настало время отследить ошибку при выводе параметров командной строки. Вспомним, что последний символ в каждом пара- метре командной строке содержал неверное значение ("мусор"). Воз- можно, неверен байт длины строки, или строковые данные портятся при последующих присваиваниях. Для выявления этого используйте окно Watch (Просмотр). Наж- мите клавиши Alt-F9, наберите parmonheap, затем нажмите Enter. Оператор for обработает в цикле все параметры командной строки, формируя связанный список и копируя каждую строку при ее поступ- лении в динамически распределяемую область памяти. Указатель Head указывает на начало списка, Tale - на последний узел в списке, а Temp используется, как временная память для выделения и инициали- зации нового узла. Так как строковые данные запорчены, нажмите клавиши Ctrl-F7 и добавьте в окно Watch следующее выражение: Tail^.Parm^ Это позволяет отслеживать строковые данные, хранящиеся в последнем узле списка. Конечно, до инициализации на строке 207 это значение будет содержать "мусор". Вместо того, чтобы выполнять программу по шагам, просто сле- дите за окном Watch в конце каждой итерации. Переместите курсор на строку 208 и нажмите клавишу F2, чтобы установить там точку останова. Теперь, чтобы выполнить программу до точки останова, нажмите клавишу F9. Если вы используете DOS версии 3.х, то в окне просмотра вы увидите полный маршрут доступа к TPDEMOB.EXE (при работе под управлением DOS 2.x вы увидите пустую строку, в этом случае просто нажмите клавишу F9 и работайте дальше). Строка дан- ных выглядит, как и требуется. Нажмите клавишу F9, чтобы выполнить цикл еще раз. Данные опять выглядят правильно. Теперь вы знаете, что строка копируется в динамически распределяемую область памяти правильно. Можно ис- пользовать окно Inspector (Проверка) и посмотреть, не повреждены ли еще данные. Переместите курсор к Head и нажмите клавиши Atl-F10 I. Нажав клавишу Enter, посмотрите на значение, на которое ссы- лается Parm. Вы смотрите на первый элемент списка, и его строко- вые данные уже повреждены. Если вы нажмете клавишу Esc, стрелку вниз, а затем снова клавишу Enter, то вы откроете окно Inspector (Проверка) для второго узла (элемента) списка. Нажмите клавишу Enter, чтобы проверить строковые данные. Они не запорчены, факти- чески, на тот же узел ссылается указатель Tail. Очевидно, что-то не так с концом строковых данных. Следите за окном Watch, когда вы используете клавишу F7 для выполнения цикла. На строке 199 содержится вызов процедуры GetMem, перед этим вызовом Tail^.Parm^ равно первому символу. Не- посредственно после вызова GetMem последний символ в Tail^.Parm^ уничтожается. Что происходит? Для каждого параметра командной строки в цикле for сначала выделяется запись, затем строковые данные, за- тем следующая запись и т.д. При вызове GetMem на строке 198 долж- но выделяться достаточно памяти для строки, плюс байта длины, но, как можно заметить, к Length(s) не прибавляется 1. Хотя на строке 199 строка успешно копируется, для нее на самом деле выделено на 1 байт меньше, чем она использует. Таким образом, первый символ строки перекрывается первым байтом следующей записи, выделенной при обращении к процедуре New(Temp). Последний параметр остается незапорченным, так как на ним не следует другая ParmRec. Это все известные нам ошибки в программе. Возможно при ее выполнении вы найдете какие-то еще. Вы можете исправить эти ошиб- ки, а затем перекомпилировать программу (для удобства они отмеча- ются двумя звездочками (**)), или запустить TPDEMO.EXE - версию программу, о которой рассказывалось в Главе 3 и в которой ошибок нет. лава 15. Виртуальная отладка с использованием процессора 80386 ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Турбо отладчик позволяет вам полностью использовать произво- дительность систем, основанных на процессоре 80386. Виртуальная отладка позволяет отлаживаемым программам полностью использовать адресное пространство, превышающее 640К (как при отсутствии в па- мяти отладчика), поскольку Турбо отладчик загружается в расширен- ную память выше 1Мб.. Отладка выполняется так же, как при обычном использовании Турбо отладчика, только когда загружается драйвер TDH386, ваша программа загружается и выполняется с того же адреса, как и при обычном выполнении (без отладчика). Это может оказаться очень по- лезным как при отладке больших программ, так и при обнаружении ошибок, которые исчезают, если программа загружается в старшие адреса памяти. Виртуальная отладка также позволяет вам наблюдать за чтением и записью в произвольные ячейки памяти или ввода-вывода, не утра- чивая при этом (или почти не утрачивая) скорости выполнения. Это без дополнительной оплаты позволяет использовать все мощные средства аппаратной отладки. Если у вас имеется процессор 80286, то с помощью отладчика, работающего в защищенном режиме (TD286) вы можете получить в свое распоряжение больше памяти, чем при обычной работе с отладчиком. Более подробно об этом рассказывается в Главе 16. лава 15. Виртуальная отладка с использованием процессора 80386 ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Турбо отладчик позволяет вам полностью использовать произво- дительность систем, основанных на процессоре 80386. Виртуальная отладка позволяет отлаживаемым программам полностью использовать адресное пространство, превышающее 640К (как при отсутствии в па- мяти отладчика), поскольку Турбо отладчик загружается в расширен- ную память выше 1Мб.. Отладка выполняется так же, как при обычном использовании Турбо отладчика, только когда загружается драйвер TDH386, ваша программа загружается и выполняется с того же адреса, как и при обычном выполнении (без отладчика). Это может оказаться очень по- лезным как при отладке больших программ, так и при обнаружении ошибок, которые исчезают, если программа загружается в старшие адреса памяти. Виртуальная отладка также позволяет вам наблюдать за чтением и записью в произвольные ячейки памяти или ввода-вывода, не утра- чивая при этом (или почти не утрачивая) скорости выполнения. Это без дополнительной оплаты позволяет использовать все мощные средства аппаратной отладки. Если у вас имеется процессор 80286, то с помощью отладчика, работающего в защищенном режиме (TD286) вы можете получить в свое распоряжение больше памяти, чем при обычной работе с отладчиком. Более подробно об этом рассказывается в Главе 16. Аппаратные средства, необходимые для виртуальной отладки ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Чтобы можно было использовать виртуальный отладчик, в вашем компьютере должен применяться процессор 80386. Вы должны распола- гать также расширенной (extended) памятью объемом не менее 640К. Если вы используете расширенную память для псевдодисков, буферов и т.д., то может потребоваться создание специальных версий файлов CONFIG.SYS или AUTOEXEC.BAT, которые нужно будет использовать при виртуальной отладке. Установка драйвера устройства для виртуального отладчика ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Перед запуском виртуального отладчика нужно убедиться, что вашем файле CONFIG.SYS установлен драйвер устройства. Установить его можно, включив в данный файл следующую строку: DEVICE = TDH386.SYS Если драйвер TDH386.SYS содержится у вас не в корневом ката- логе, а в другом месте, то нужно указать маршрут доступа. Обычно виртуальный отладчик позволяет вам использовать до 256 байт строк, задающих параметры операционной среды DOS. Если этого недостаточно, или вам не нужен такой объем и вы хотите сэ- кономить возможно больше памяти, используйте в файле CONFIG.SYS параметр -e, который задает число байт операционной среды. Напри- мер: DEVICE = TDH386.SYS -e2000 резервирует 2000 байт для переменных операционной среды DOS. Запуск виртуального отладчика ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Запускается виртуальный отладчик точно также, как обычный сеанс работы с Турбо отладчиком командой: TD386 [параметры] [программа [параметры_программы]] Другими словами, вместо TD указывается просто TD386. При этом будет выполняться поиск выполняемой программы Турбо отладчи- ка и загрузка ее в расширенную память. Если у вас есть другие программы или драйверы устройств, ис- пользующие расширенную память (псевдодиски, буферы и др.), вы должны указать TD386, сколько памяти занимают эти программы. Это можно сделать с помощью параметра командной строки -e, за которым указывается объем (в килобайтах) расширенной памяти, используемой другими программами, например: TD386 -e512 myprog Эта командная строка сообщает TD386, что вы хотите зарезер- вировать для других программ первые 512К расширенной памяти. Обычно, если в вашей системе поддерживается стандарт XMS, вовсе не обязательно сообщать TD386, сколько памяти нужно оста- вить для программ в расширенной памяти - программы уже передали эту информацию TD386. Параметр -e нужно использовать только с программами (такими, как VDISK), которые не взаимодействуют со стандартом XMS. Поскольку вы, вероятно, всегда резервируете один и тот же объем расширенной памяти, TD386 дает вам способ постоянного зада- ния объема резервируемой памяти. Чтобы сообщить, что вы хотите постоянно установить значение параметра -e в выполняемом файле TD386, используйте параметр -w. Вам будет выведена подсказка, в ответ на которую нужно ввес- ти имя выполняемой программы. Если вы работаете в DOS версии 3.0 или старше, в подсказке будет указываться маршрут доступа к ката- логу и имя файла, из которого запущен TD386. Вы можете использо- вать это имя, нажав клавишу Enter, или ввести имя нового выполня- емого файла. Файл с этим именем должен существовать и представлять собой копию программы TD386. Если вы работаете под управлением DOS версии 2.х, вам при- дется указать полное имя выполняемой программы TD386 (с маршру- том). Перечислим параметры командной строки TD386.EXE: ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД -?, -h Выводит справочную информацию по TD386. -b Позволяет вам прерывать выполнение программы с помощью клавиш Ctrl-Break, даже когда запрещены прерывания. -e#### Задает, сколько килобайт расширенной памяти используют- ся другими программами или отлаживаемой вами программой (данный параметр указывать не требуется, если ваша систе- ма поддерживает стандарт XMS). -w Модифицирует TD386.EXE новым используемым по умолчанию значением -e или -f. -f#### Разрешает эмуляцию EMS с помощью страничного обмена (в расширенной памяти) и устанавливает сегмент границы стра- ницы в значение #### (шестнадцатиричное). Последние три цифры должны быть равны 000 (например, E000 или C000). Заметим, что данный параметр применяется только к вызовам EMS Турбо отладчика. Если вы не можете загрузить таблицу идентификаторов, попробуйте использовать параметр -f, чтобы вынудить TD386 заимствовать из расширенной памяти. Нет EMS: -fD000 EMS по адресу D000: -fE000 EMS по адресу E000: -fD000 -f- Запрещает эмуляцию EMS (отменяет действие предыдущего па- раметра командной строки). -w Модифицирует TD386.EXE новым используемым по умолчанию значением параметра -e или -f. Вы можете ввести имя ново- го выполняемого файла, который еще не существует. При этом TD386 создаст новый выполняемый файл. ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Заметим, что параметры TD386 должны указываться в командной строке перед другими параметрами Турбо отладчика или именем прог- раммы, например: TD386 -e1024 -fD000 -w резервирует 1024К расширенной памяти, разрешает эмуляцию EMS со страничной рамкой D000, и модифицирует TD386.EXE данными значени- ями. Для вывода списка всех командных строк, которые можно ис- пользовать для TD386, наберите просто TD386 ? или TD386 -h и наж- мите клавишу Enter. Примечание: Если вы работаете на компьютере с процес- сором 386 и хотите прочитать параметры командной строки TD386.EXE, нужно перезагрузить TDH386.SYS. Отличия обычной и виртуальной отладки ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД В основном при обычной отладке и при виртуальной отладке с использованием возможностей процессора 80386 все работает одина- ково. Различия состоят в следующем: 1. При использовании команды FileіDOS Shell (ФайліКомандный процессор операционной системы DOS) отлаживаемая програм- ма никогда не сбрасывается на диск. Это означает, что иногда вам может не хватить памяти для запуска других программ в ответ на подсказку DOS. 2. Ваша программа может использовать почти все инструкции процессора 80386, за исключением инструкций защищенного режима: CLTS, LMSW, LTR, LGDT, LIDT, LLDT. 3. Хотя при виртуальной отладке вы можете использовать все режимы расширенной адресации процессора 80386 и 32-раз- рядные регистры, вы не можете обращаться к памяти свыше 1 Мб. При попытке сделать это будет генерироваться прерыва- ние, и управление будет передано отладчику. 4. Нельзя использовать виртуальную отладку, если вы уже за- пустили программу или драйвер устройства, использующие виртуальный и защищенный режимы процессора 80386. Это та- кие программы, как: - операционная среда DesqView; - операционная среда Windows-386; - драйвер эмуляции EMS CEMM.SYS Compaq; - драйвер эмуляции EMS QEMM.SYS QuarterDeck; - 386^MAX. Если вы используете обычно одну из этих программ, вам придется остановить их или разгрузить перед использовани- ем TD386. 5. Если вы используете виртуальную отладку, TD386 может пе- рехватывать генерируемые вашей программой прерывания. Ес- ли происходит прерывание, программа останавливается и TD386 сообщает о том, что произошло прерывание. Выводимое сообщение описывает характер прерывания, а стрелка в об- ласти кода окно CPU (ЦП) или в окне Module (Модуль) отме- чает инструкцию, которая вызвала прерывание. * 6. Непредвиденных прерываний возникать не должно. В случае их возникновения обратитесь к следующему разделу и про- верьте, упоминаются ли они там. Если нет, проконсульти- руйтесь с представителями фирмы Borland. Замечания относительно возможных проблем ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Если вы используете TDH386 и получаете сообщение "Not enouhg memory to load symbol table" ("Не хватает памяти для загрузки таблицы идентификаторов"), то вам нужно разрешить для TDH386 эмуляцию EMS. Например, чтобы установить для TDH386 EMS в сегмент 0D000h, используйте для запуска TDH386 следующую команду: TDH386 -FD000 Если вы используете HP Vestra и получаете при запуске TDH386 непредвиденное прерывание 06, нужно задать параметр в установке CMOS. По умолчанию серия Vestra использует в части HP-HILL инс- трукцию защищенного режима. Чтобы обойти это, свяжитесь с фирмой Hewlett Packard и узнайте, как обойти данную инструкцию. Если исключительная ситуация 06 возникает после того, как вы некоторое время поработаете в TDH386, то ваш исходный код будет, возможно, модифицирован. Обычно исключительная ситуация 06 гене- рируется процессором 80386, когда встречается недопустимый код операции. Типичной причиной этой ошибки является использование неинициализированных указателей. Исключительные ситуации 06, 13 и 0D могут возникать, если вы используете старый драйвер "мыши", сетевой драйвер или другой аппаратный драйвер. Если в TD386 вы получаете данные ошибки, по- пытайтесь удалить по очереди аппаратные драйверы, начиная с драй- вера "мыши", сетевого драйвера, и так далее, пока не идентифици- руете драйвер, приводящий к такой ситуации. Если для этого драй- вера имеются модификации, то посмотрите, устранит ли проблему их установка. Последняя возможная мера состоит в полном удалении драйвера. Если вы получаете во время загрузки TDH386 сообщения "Processor already in protected mode" ("Процессор уже в защищен- ном режиме"), это означает, что выполняется программа, использую- щая виртуальный режим процессора 80386 (например,QEMM). Использо- вать одновременно эти программы и TDH386 нельзя. Если вам необ- ходимо использовать данные подсистемы управления памятью, попы- тайтесь вместо TDH386 использовать TD286. Сообщения об ошибках TD386 ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД При невозможности начать работу TD386 генерирует одно из следующих сообщений и возвращает вас на уровень подсказки DOS. Перед тем, как запустить TD386, нужно исправить ситуацию. TD386 error: 80386 device driver missing or wrong version (нет драйвера 80386 или неправильная версия) Перед вызовом TD386 с помощью командной строки DOS нужно ус- тановить в файле CONFIG.SYS драйвер TDH386.SYS. TD386 error: Can't enable the A20 adress line (не могу разрешить адресацию строки A20) TDH386 не может обратиться к памяти свыше 1Мб. Это может происходить в том случае, если вы работаете в системе, которая не полностью совместима с IBM. TD386 error: Can't find TD.EXE (невозможно найти TD.EXE) TD386 не может найти файл TD.EXE. TD386 error: Couldn't execute TD.EXE (невозможно выполнить TD.EXE) TD386 не может выполнить TD.EXE. TD386 error: Enviroment too long; use -e#### switch with TDH386.SYS (слишком длинная строка операционной среды, используйте TDH386.SYS с параметром -e####) Нужно изменить параметр -e, как было указано в предыдущих разделах. TD386 error: Not enough Extended Memory avaliable (объем доступной расширенной памяти недостаточен) TD386 превысил границы памяти. Нужно использовать машину с памятью большего объема или освободить память (уменьшив, напри- мер, объем псевдодиска). TD386 error: Wrong CPU type (not an 80386) (неправильный тип центрального процессора: не 80386) Вы работаете на системе, где используется процессор, отлич- ный от 80386. Следующие ошибки могут произойти, если вы модифицировали TD386 с помощью параметра -w: TD386 error: Cannot open program file (невозможно открыть файл программы) TD386 error: Cannot read program file (невозможно прочитать файл программы) TD386 error: Cannot write program file (невозможно записать файл программы) TD386 error: Program file corrupted or wrong version (программный файл поврежден или неверна версия) Сообщения об ошибках TDH386.SYS ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Может возникнуть только две ошибки, связанных с драйвером TDH386.SYS: Wrong CPU type: TDH driver not installed (неверный тип ЦП: драйвер TDH не установлен) Invalid command line: TDH driver not installed (недопустимая командная строка: драйвер TDH не установлен) лава 16. Отладка в защищенном режиме с использованием TD386 ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Отладчик для защищенного режиме TD386 использует преимущест- ва процессора 80286 и позволяет освободить больше памяти для от- лаживаемых вами программ. TD386 помещает программу Турбо отладчи- ка в расширенную память выше 1Мбайта и оставляет в нижних 640К памяти сравнительно небольшой загрузчик. Это дает вам больше мес- та для программ, которые вы отлаживаете, и их таблиц идентифика- торов. Используйте в этом случае Турбо отладчик, как обычно. Единс- твенным отличием будет то, что вашей программа получит больший объем памяти. Замечание: Если вы работаете на компьютере с процессо- ром 80386 и еще не используете программу защищенного режима типа 386^MAX, то еще большие возможности и экономию памяти вам даст отладчик TD386. Более подробно об этом рассказыва- ется в Главе 15. Аппаратура, необходимая для использования отладчика TD286 ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Чтобы использовать отладчик защищенного режима TD286, вы должны иметь компьютер с процессором 80286 или старше и не менее 640К доступной расширенной памяти. Установка отладчика для защищенного режима ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Перед тем, как в первый раз использовать TD286, вы должны запустить программу конфигурации TD286INS. Это позволит TD286 оп- ределить некоторые характеристики аппаратуры, на которой вы рабо- таете. Для настройки конфигурации TD286 запустите программу TD286INS в ответ на подсказку DOS. Программа TD286INS после того, как она определит характерис- тики аппаратных средств, попросит вас нажать несколько раз клави- шу пробела. Если в какой-либо момент ваша система "зависнет" и не будет дальше работать, то перезагрузитесь и повторно запустите программу конфигурации. Программа конфигурации знает о том, что возникла проблема, и продолжает выполнять следующую фазу тестиро- вания. Когда программа TD286INS завершит выполнение, можно будет использовать TD286. Запуск отладчика для защищенного режима ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Запустить отладчик для защищенного режима можно с помощью командной строки со следующим синтаксисом: TD286 [параметры] [программа [параметры программы]] Параметры TD286 совпадают с параметрами обычного отладчика. за исключением параметров -r, -rn, -rp, -rs, -sm, -w, -y, -ye. (TD286 не работает с оверлеями, удаленной отладкой и Windows). Отличия Турбо отладчика и отладчика для защищенного режима ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Есть несколько функций, которые вы можете выполнять в Турбо отладчике, но не можете использовать в отладчике TD286: - когда вы используете команду FileіDos Shell (ФайліКоманд- ный процессора DOS) для выполнения команды DOS, программа, которую вы отлаживаете, не сбрасывается на диск. Это озна- чает, что вам может не хватить памяти для выполнения прог- рамм в ответ на подсказку DOS. - вы не можете использовать отладчик TD286 для отладки прог- рамм, работающих в защищенном режиме, или использовать расширитель DOS, который приводит к конфликту с TD286. Отладка программ, использующих дополнительную память ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД По умолчанию TD286 будет использовать всю доступную расши- ренную память. Если вы отлаживаете программу, которая сама ис- пользует этот вид памяти (extended memory), то для использования TD286 нужно создать файл конфигурации CONFIG.286 в корневом ката- логе текущего дисковода. В этом файле должна быть строка: MEGS=# где # - это объем расширенной памяти, которую может использовать отладчик. Выполнение TD286 на разных машинах ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Отладчику TD286 известны аппаратные характеристики десятков машин. Когда вы запускаете TD286INS, и она сообщает "Machine already in file's database" (машина уже описана в файле базы дан- ных), то TD286 уже знает о вашем компьютере, и модификация не требуется. Если TD286INS выполнит свои проверки (тестирование), то ап- паратные характеристики вашей машины будут записаны в TD286, и будет создан файл с расширением .DB. Этот файл следует послать фирме Borland или на одну из конференций Compuserve, благодаря чему следующие версии TD286 смогут автоматически поддерживать ха- рактеристика вашего компьютера. TD286 может хранить характерис- тики до 10 компьютеров, отличных от того, на котором он начал ра- ботать. Оглавление Глава 9. Выражения......................................... Выбор языка для вычисления выражения....................... Адреса кода, адреса данных и номера строк.................. Доступ к идентификаторам вне текущей области действия...... Синтаксис переопределения области действия................. Переопределение области.................................... Некоторые замечания по переопределению области действия.... Переопределение области действия в программах Паскаля...... Некоторые замечания по переопределению области действия.... Область действия и DLL..................................... Неявная область действия при вычислении выражений.......... Списки байт................................................ Выражения языка Си......................................... Идентификаторы языка Си.................................... Регистровые псевдопеременные языка Си...................... Форматы констант и чисел языка Си.......................... Символьные строки и ESC-последовательности языка Си........ Операции языка Си и старшинство операций................... Выполнение в программе функций на Си....................... Выражения языка Си с побочными эффектами................... Ключевые слова языка Си и преобразование типов............. Выражения языка Паскаль.................................... Идентификаторы Паскаля..................................... Константы Паскаля и формат чисел........................... Строки Паскаля............................................. Операции Паскаля........................................... Соглашения Паскаля по вызовам процедур и функций........... Выражения Ассемблера....................................... Идентификаторы Ассемблера.................................. Константы Ассемблера....................................... Операции Ассемблера........................................ Управление форматом........................................ Глава 10. Объектно-ориентированная отладка для Паскаля и C++....................................................... Отладка объектно-ориентированных программ Турбо Паскаля.... Окно Hierarchy............................................. Области списка типов объектов и классов.................... Локальное меню области области списка типов объектов и классов................................................... Команда Inspect............................................ Команда Tree............................................... Область дерева иерархии.................................... Локальные меню области дерева иерархии..................... Локальное меню области дерева порождающих классов.......... Окно Object Type/Class Inspector........................... Локальное меню окна Object Type/Class Inspector............ Область полей данных объекта (верхняя)..................... Область методов объекта (нижняя)........................... Окно Object/Class Instance Inspector....................... Локальное меню окна Object/Class Instance Inspector........ Средняя и нижняя область................................... Глава 11. Отладка на уровне Ассемблера..................... Когда недостаточно отладки на уровне исходного кода........ Окно CPU................................................... Область кода............................................... Дисассемблер............................................... Локальное меню области кода................................ Команда Goto............................................... Команда Origin............................................. Команда Follow............................................. Команда Caller............................................. Команда Previous........................................... Команда Search............................................. Команда View Source........................................ Команда Mixed.............................................. Команда New CS:IP.......................................... Команда Assemble........................................... Команда I/O................................................ Команда In Byte............................................ Команда Out Byte........................................... Команда Read Word.......................................... Команда Write Word......................................... Область регистров и флагов................................. Локальное меню области регистров........................... Область селектора.......................................... Область данных............................................. Локальное меню области данных.............................. Область стека.............................................. Ассемблер.................................................. Переопределения размера адреса операнда.................... Память и непосредственные операнды......................... Переопределение размера данных в операндах................. Строковые инструкции....................................... Окно Dump.................................................. Окно Registers............................................. Глава 12. Сопроцессор 80х87 и эмулятор..................... Сопроцессор 80х87 или эмулятор?............................ Окно Numeric Processor..................................... Область регистров.......................................... Локальное меню области регистров........................... Область управления......................................... Локальное меню области управления.......................... Область состояния.......................................... Локальное меню области состояния........................... Глава 13. Команды Турбо отладчика.......................... Оперативные клавиши........................................ Команды, доступные из основного меню....................... Меню Ё (системное меню).................................... Меню File (Файл)........................................... Меню Edit (Редактирование)................................. Меню View (Обзор).......................................... Меню Run (Выполнение)...................................... Меню Breakpoints (Точки останова).......................... Меню Data (Данные)......................................... Меню Options (Параметры)................................... Меню Window (Окно)......................................... Меню Help (Справка)........................................ Команды локальных меню..................................... Локальное меню окна Breakpoints (Точки останова)........... Меню окна CPU (ЦП)......................................... Область кода............................................... Область селектора.......................................... Область данных............................................. Область флагов............................................. Область стека.............................................. Окно Dump (Дамп)........................................... Меню окна Execution History................................ Область инструкций......................................... Область регистрации нажатий клавиш......................... Окно File (Файл)........................................... Локальное меню окна Log (Регистрация)...................... Окно Module (Модуль)....................................... Окно Windows Messages...................................... Область выбора окна........................................ Область класса сообщений................................... Область сообщений.......................................... Окно Clipboard............................................. Окно Numeric Proseccor (Сопроцессор)....................... Область регистров.......................................... Область состояния.......................................... Область управления......................................... Окно Hierarchy (Иерархия).................................. Область списка типов объектов/классов...................... Область дерева иерархии.................................... Область дерева порождающих объектов/классов................ Меню окна Registers (Регистры)............................. Окно Stack (Стек).......................................... Окно Variables (Переменные)................................ Область локальных идентификаторов.......................... Окно Watches (Просмотр).................................... Окно Inspector (Проверка).................................. Окно проверки типа объекта/класса.......................... Окно проверки экземпляра объекта........................... Области текста............................................. Области списков............................................ Команды в окнах подсказки.................................. Клавиатурные команды диалогового окна Таблица 13.4.... Команды перемещения окна................................... Трафаретные символы, используемые при поиске............... Полное дерево меню......................................... Глава 14. Отладка программы................................ Когда что-то не работает................................... Стиль отладки.............................................. Полное выполнение.......................................... Последовательное тестирование.............................. Типы ошибок................................................ Общие ошибки............................................... Скрытые эффекты............................................ Предположения об инициализации данных...................... Не забывайте об очистке.................................... "Забор и столбы"........................................... Ошибки, специфические для языка Си......................... Использование неинициализированных локальных переменных.... Не следует путать = и ==................................... Не следует путать старшинство операций..................... Неверные арифметические действия с указателями............. Не забывайте о расширении по знаку......................... Помните об усечении........................................ Использование точки с запятой.............................. Макрокоманды с побочными эффектами......................... Повторение имен локальных динамических переменных.......... Неправильное использование динамических локальных переменных................................................ Функция возвращает неопределенное значение................. Неправильное использование ключевого слова break........... Код, не приводящий к результату............................ Ошибки, специфические для Паскаля.......................... Инициализированные переменные.............................. Неправильная работа с указателями.......................... Неправильное использование области действия................ Неправильное использование точки с запятой................. Функция возвращает неопределенное значение................. Уменьшение значения переменных размером в байт или слово... Игнорирование границ и особые случаи....................... Ошибки диапазона........................................... Ошибки, специфические для Ассемблера....................... Программист забывает о возврате в DOS...................... Программист забывает об инструкции RET..................... Генерация неверного типа возврата.......................... Неправильный порядок операндов............................. Программист забывает о стеке или резервирует маленький стек Вызов подпрограммы, которая портит содержимое нужных регистров................................................. Ошибки при использовании условных переходов................ Неверное понимание работы префикса REP..................... Нулевое содержимое CX и работа с целым сегментом........... Неправильная установка флага направления................... Ошибки при повторении команд сравнения строк............... Ошибки при назначении сегмента строк....................... Неправильное преобразование из байта в слово............... Использование нескольких префиксов......................... Необязательные операнды в командах обработки строк......... Уничтожение содержимого регистра при умножении............. Ошибки, связанные с изменением содержимого регистров....... Изменение состояния флага переноса......................... Программист долго не использует флаги...................... Смешение операндов в памяти и непосредственных операндов... Ошибки, связанные с изменением содержимого регистров....... Изменение состояния флага переноса......................... Программист долго не использует флаги...................... Смешение операндов в памяти и непосредственных операндов... Ошибки, связанные с возвратом в начало сегмента............ Сохранение содержимого регистров при обработке прерываний.. Игнорирование групп в таблицах операндов и данных.......... Проверка................................................... Проверка граничных условий и случаи ограничения............ Ввод ошибочных данных...................................... Отсутствие входных данных.................................. Отладка, как часть процесса создание программы............. Пример сеанса отладки...................................... Сеанс отладки программы на языке Си........................ Поиск ошибок............................................... Разработка плана действий.................................. Запуск Турбо отладчика..................................... Проверка................................................... Окно Watch................................................. Диалоговое окно Evaluate/Modify............................ Эврика!.................................................... Сеанс отладки с использованием программы на Паскале........ Поиск ошибок............................................... Выбор стратегии поиска ошибок.............................. Запуск Турбо отладчика..................................... Перемещение по программе................................... Диалоговое окно Evaluate/Modify............................ Проверка................................................... Выражения просмотра........................................ Следующая ошибка........................................... Глава 15. Виртуальная отладка с использованием процессора 80386..................................................... Аппаратные средства, необходимые для виртуальной отладки... Установка драйвера устройства для виртуального отладчика... Запуск виртуального отладчика.............................. Отличия обычной и виртуальной отладки...................... Замечания относительно возможных проблем................... Сообщения об ошибках TD386................................. Сообщения об ошибках TDH386.SYS............................ Глава 16. Отладка в защищенном режиме с использованием TD386..................................................... Аппаратура, необходимая для использования отладчика TD286.. Установка отладчика для защищенного режима................. Запуск отладчика для защищенного режима.................... Отличия Турбо отладчика и отладчика для защищенного режима. Отладка программ, использующих дополнительную память....... Выполнение TD286 на разных машинах......................... |