Окно Watch
Окно Watch занимает высокое место в списке важных свойств отладчика Visual C++. Что делает окно Watch чрезвычайно популярным — так это его разносторонность. Можно по-разному работать с этим окном, собирая информацию о приложении. Самая впечатляющая возможность заключается в том, что оно позволяет легко изменять" значение переменной, редактируя его в правой стороне окна. Вспомните обсуждение синтаксиса для выражений точек прерывания. В окне Watch используется такая же оценка выражений, поэтому и синтаксис расширенных точек прерывания, и правила составления выражений, и псевдорегистры могут использоваться и в окне Watch.
Форматирование данных и оценка выражений
Чтобы приобрести опыт манипулирования окном Watch, необходимо запомнить форматирующие символы, приведенные в табл. 5.3 и 5.4, которые получены из документации Visual C++ на MSDN. Форматный символ записывают через запятую после переменной. Наиболее полезный спецификатор формата для СОМ-программирования — hr. Если разместить в окне Watch выражение @ЕАХ,hr, то при переходе через вызов СОМ-метода можно увидеть результат вызова в понятной форме (@ЕАХ — это регистр Intel CPU, в котором хранятся возвращаемые значения). Применение форматных спецификаторов позволяет легко управлять видом данных и экономить огромное количество времени на их интерпретацию.
Таблица 5.3. Символы форматирования для переменных окна Watch
Символ |
Описание формата |
Пример |
Вид на экране |
d, i |
Десятичное целое со знаком |
(int)OxFOOOF065,d |
-268373915 |
u |
Десятичное целое без знака |
0x0065, u |
101 |
о |
Восьмеричное целое без знака |
OxF065,o |
0170145 |
х, X |
Шестнадцатеричное целое |
61541, X |
OxOOOOF065 |
1 , h |
Префикс long или short для d, i, u, о, х, X |
0x00406042, hx |
OxOc22 |
£ |
Плавающая точка со знаком |
3./2.,f |
1.500000 |
е |
Научная нотация со знаком |
3./2,е |
1.500000e+000 |
g |
Плавающая точка или научная нотация со знаком, в зависимости от того, какой формат короче |
3./2,g |
1.5 |
с |
Одиночный символ |
0x0065, с |
'e1 |
s |
Строка |
szHiWorld, s |
"Hello world" |
su |
Строка Unicode |
szWHiWorld, su |
"Hello world" |
st |
Строка Unicode или ANSI, в зависимости от установки в AUTOEXP.DAT |
|
|
hr |
HRESULT или Win32 код ошибки |
0x00000000, hr |
S_OK |
we |
.Флажок класса Windows |
0x00000040, we |
WC_ DEFAULTCHAR (Заметим, что хотя этот формат и докуме
нтирован он не работает в Visual C++ 6) |
wm |
Номера Windows-сообщений |
0x0010, wm |
WM_CLOSE |
Таблица 5.4. Символы форматирования для дампов памяти окна Watch
Символ |
Описание формата |
Пример |
Вид на экране |
mа |
64 ASCII символы |
Ox0012ffac,ma |
0x001 2ffac .4.0.".OW&.
.1W&.0.:W.1 .."1.JO&.12. ."1..0y.1 |
m |
16 байт (шестнад-цатеричных), за которыми следует 16 ASCII символов |
Ox0012ffac,m |
0x001 2ffac
ЬЗ 34 cb 00 84 30 94 80 ff 22 8а 30 57 26 00 00 .4...0...".OW&.. |
rob |
16 байт (шестнад-цатеричных), за которыми следует 16 ASCII символов |
Ox0012ffac,mb |
0x001 2ffac ЬЗ 34 cb 00 84 30 94 80 ff 22 8a 30 57 26 00 00 A..O...".OW&.. |
mw |
8 слов |
Ox0012ffac,mw |
0x001 2ffac 34b3 OOcb 3084 8094 22ff 308a 2657 0000 |
md |
4 двойных слова |
Ox0012ffac,md |
0x001 2ffac ООсЬ34ЬЗ 80943084 308a22ff 00002657 |
mq |
4 четверных слова |
Ox0012ffac,mq |
0x001 2ffac 8094308400cb34b3 00002657308a22ff |
mu |
2-байтовые символы (Unicode) |
Ox0012ffac,mu |
0x001 2ffac 34ЬЗ OOcb 3084 8094 22ff 308a 2657 0000 7 99797 |
# |
(Не документирован). Расширяет указатель (на область памяти) на указанное число значений |
pCharArray, 10 |
Расширенный массив из 10 символов (использующий расширители + /-) |
Если имеется большой массив, то можно сместить указатель к его середине, а в расширении указать то количество значений, которое требуется отобразить. Например, переменная с форматом (pBigArray+100) ,20 показывает в окне Watch 20 элементов со смещением 99. При этом имеется ошибка: значения индекса всегда начинаются с 0, независимо от позиции первого отображенного элемента от начала массива. В примере с pBigArray первый индекс, показанный как.0, является 100-м элементом массива, второй — 101-м элементом и т. д.
Кроме широких возможностей форматирования данных по желанию разработчика, окно Watch позволяет выполнять приведение типов переменных и показывать их в любой нужной ему форме. Например, чтобы получить смещения указателя, можно использовать выражения BY, WO и DW. Разрешены также адресная операция (&) и операция указателя (*). Обе они позволяют получать значения адресов памяти и наблюдать результаты операций приведения типов. В окне Watch можно явно указывать контекст переменной, если применять спецификаторы контекста, описанные выше в разделе "Синтаксис расширенных точек прерывания и позиционные точки прерывания" этой главы. Наконец, все форматы и спецификаторы, используемые в окне Watch, работают также и в окне QuickWatch.
Нетрудно заметить, что окно Watch предоставляет гораздо больше возможностей, чем простой просмотр статических переменных. Фактически, это средство оценки выражений, в котором можно проверять любые условные операторы. Во время тестирования элементов своих программ я очень широко использую окно Watch для проверки операторов if и других условных операторов. Для этого необходимо поместить в окно Watch индивидуальные переменные условного оператора, а вслед за ними — сам условный оператор. Это позволяет видеть как значения каждой переменной, так и результаты условной оценки. Для того чтобы изменить оценку условного оператора, можно сначала изменять значения индивидуальных переменных и прослеживать вывод в окне Watch. Из-за того что окно Watch так хорошо управляет выражениями, программисту не нужно для несложных вычислений открывать программу Калькулятор.
Простые вычисления можно выполнять прямо в окне Watch.
Таймирование кода в окне Watch
Еще один изящный прием — применение окна Watch для наблюдения за временем выполнения участков программы. В качестве элементарного таймера может служить недокументированный псевдорегистр @CLK. Во многих случаях нужно только грубое представление временного интервала между двумя точками программы, и @CLK помогает уточнить время выполнения между двумя точками прерывания. Имейте в виду, что это время включает накладные расходы отладчика. Весь фокус заключается в том, чтобы ввести (в окно Watch) два элемента наблюдения @CLK: первый — просто "@CLK" и второй — "@CLK=0". Второй элемент сбросит таймер (установит на 0) после того, как выполнение возобновится. Поскольку время измеряется в микросекундах, а я предпочитаю — в миллисекундах, то устанавливаю первый @CLK в формате @CLK/1000,d. Форматный символ ,d введен для того, чтобы показать таймер в виде десятичного числа, если в окне Watch предварительно установлен шестнадцатеричный формат отображения (Hexadecimal Display). Хотя таймер @CLK и недостаточно совершенен, но для приближенных подсчетов он вполне пригоден.
Вызов функций в окне Watch
И последнее: окно Watch наделено способностью выполнять функции внутри отладчика. Можно задать вопрос: "А зачем это нужно?" А для того, чтобы полностью настроить отладочную среду на требования пользователя. Например, вместо десятиминутного просмотра 10 различных структур данных (чтобы убедиться в их однородности), можно написать специальную функцию, которая проверяет данные, и затем вызывать ее прямо из окна Watch, когда это необходимо (например, когда отладчик останавливает приложение).
Отметим, однако, что сама программа никогда не должна вызвать такие функции — они должны использоваться только в окне Watch. В отладочных сборках все функции компонуются вместе с программой, а в выпускных сборках функции, которые вызываются из окна Watch, с программой не компонуются.
При вызове функции в окне Watch ей можно передавать параметры, а это позволяет создавать функции-шаблоны, работающие на различных типах данных. Окно Watch можно представлять себе как ограниченное окно Immediate из IDE Microsoft Visual Basic.
Если отладочная функция не имеет параметров, не забывайте использовать при ее вызове круглые скобки с пустым списком параметров, чтобы указать отладчику Visual C++, что вызывается функция, а просматривается значение переменной. Например, если речь идет об отладочной функции void MyMemCheck (), надо вызвать ее в окне Watch в формате MyMemCheck (). Если же отладочная функция получает параметры, просто передайте их ей, как будто вызываете обычную функцию1. Если отладочная функция возвращает значение, то оно будет выведено в правой части окна Watch.
При вызове отладочных функций в окне Watch вы встретитесь с рядом ограничений. Эти ограничения не вызывают никаких трудностей, если придерживаться нескольких правил. Первое правило: пока функция находится в окне Watch, она может выполняться только в однопоточном контексте. Если имеется многопоточная программа, необходимо ввести отладочную функцию в окно Watch, проверить результаты и затем немедленно удалить ее из окна. Если отладочная функция выполнится в другом потоке (отличающемся от первого), то второй поток немедленно завершится. Второе правило: отладочная функция должна выполняться меньше чем за 20 секунд; если за это время она выдаст исключение, то отладчик завершит всю программу. Последнее правило: такая функция должна только читать память и проверять данные. Если возникает проблема, просто вызовите функцию OutputDebugstring или printf. Причем неизвестно, что может случиться, если программист начнет изменять память или вызывать API-функции Windows.
То есть укажите в круглых скобках вслед за именем функции список соответствующих аргументов. — Пер.
Имейте в виду, что отлаживающая функция выполняется всякий раз, когда окно Watch переоценивает находящиеся в нем выражения.
Это случается при следующих условиях:
- если при выполнении программы срабатывают точки прерывания; П при одношаговом проходе строки или инструкции;
- если по завершении редактирования текста отладочной функции (в левой части окна Watch) нажата клавиша <Enter>;
- когда происходит исключение в выполняющейся программе, и управление передается опять в отладчик.
Автоматическое расширение собственных типов
Хотя документация Visual C++ только упоминает эту тему, программист может создавать собственные типы, автоматически расширяемые в окне Watch, так же, как и в окнах QuickWatch и DataTips. Вы, вероятно, видели некоторые общие типы, такие как cobject и RECT, расширенные в окне Watch. Так вот, можно легко организовать дело таким образом, чтобы выгоду из расширяемости окна Watch извлекали ваши собственные типы. Весь фокус заключается в текстовом файле AUTOEXP.DAT из подкаталога Microsoft Visual Studio\Common\MSDev98\Bin. Просто добавьте в конец файла вход для своих собственных типов.
В качестве примера рассмотрим добавление входа с авторасширением для структуры PROCESS_INFORMATION, которая посылается в API-функцию createProcess. Первый шаг состоит в проверке того, что отладчик Visual C++ распознает в качестве типа. В примере программы переменная PROCESS_INFORMATION помещена в окно Watch, на ней выполнен щелчок правой кнопкой мыши и выбран пункт Properties контекстного меню. В диалоговом окне Program Variable Properties в качестве метки (имени) типа указан идентификатор _PROCESS_INFORMATION, который, если посмотреть на определение структуры, приведенное ниже, соответствует метке структуры.
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadld;
} PROCESS_INFORMATION
Документация в AUTOEXP.DAT говорит, что формат для авторасширяемого входа таков:
тип = [текст]<член[,формат]>....
В табл. 5.5 показаны значения каждого поля этого формата. Обратите внимание, что в авторасширении можно показать несколько членов.
Таблица 5.5. Входы авторасширений в AUTOEXP.DAT
Поле
|
Описание
|
Тип |
Имя типа. Для шаблонных типов, за этим полем может следовать поле со звездочкой "<*>", чтобы охватить все производные типы |
Текст |
Любой литеральный текст. Эта поле, в общем случае, есть имя члена или его короткая версия |
Член |
Фактический член данных, который будет показан в окне Watch. Этим полем может быть выражение (так, если нужно добавить некоторые смещения к различным указателям, то в вычисление можно включить смещения). Работают также операции приведения типов |
Формат |
Дополнительные спецификаторы формата для членов-переменных. Это те же спецификаторы, что и форматирующие символы, показанные в табл. 5.3 |
_PROCESS_INFORMATION=hProcess=<hProcess,Х> hThread=<hThread,X>
Спецификатор формата , х указывает, что значения отображаются в шестнадцатеричной форме.
В файле AUTOEXP.DAT можно увидеть один специальный форматирующий код— <,t>. Этот код просит отладчик разместить в качестве имени типа имя максимального (по уровню) производного типа. Например, если имеется базовый класс А с производным классом В, и только А имеет правило авторасширения, то авторасширением для переменной типа В будет имя класса В, за которым следует правило авторасширения для класса А. Формат <, t> очень полезен для прямой поддержки классов.