Как пользоваться точкой останова в с
Перейти к содержимому

Как пользоваться точкой останова в с

  • автор:

Отладка кода

В C#, как и в других появившихся до .NET языках, главная методика по отладке состоит в добавлении точек останова и изучении того, что происходит в коде в конкретные моменты во время его выполнения.

Точки останова

Точку останова (breakpoint) в Visual Studio можно помещать на любую строку кода, которая в действительности выполняется. Самый простой способ — щелчок на необходимой строке в окне редактора кода внутри затененной области вдоль левого края окна документа (или выделение нужной строки и нажатие клавиши <F9>). Это приводит к размещению в данной строке точки останова, которая вызывает прерывание процесса выполнения и передачу управления отладчику. Как и в предыдущих версиях Visual Studio, точка останова обозначается большим кружком слева от соответствующей строки в окне редактора кода. Кроме того, Visual Studio выделяет саму строку, отображая ее текст и фон разными цветами. Щелчок на кружке приводит к удалению точки останова.

Если останов на определенной строке каждый раз не подходит для решения имеющейся проблемы, можно создать так называемую условную точку останова. Для этого выберите в меню Debug (Отладка) пункт Windows — Breakpoints (Окнo — Точки останова). Откроется диалоговое окно, позволяющее указать желаемые детали для точки останова. В этом окне можно выполнять следующие действия:

Указать, что выполнение должно прерываться лишь после прохождения точки останова определенное количество раз.

Указать, что точка останова должна вступать в действие при каждом n-ном достижении строки, например, при каждом 20-м ее выполнении (это удобно при отладке больших циклов).

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

Слежения

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

Для просмотра значений переменных можно также использовать окно Autos (Автоматические). Окно Autos представляет собой окно с вкладками, которое появляется лишь тогда, когда программа выполняется в режиме отладки. Если вы его не видите, попробуйте выбрать в меню Debug (Отладка) пункт Windows — Autos (Окна — Автоматические).

В этом окне рядом с переменными, которые являются классами или структурами, отображается пиктограмма с изображением знака «плюс», щелчок на которой позволяет развернуть представление переменной и просмотреть значения ее полей.

Три предлагаемых в этом окне вкладки предназначены для наблюдения за переменными трех разных категорий:

Вкладка Autos (Автоматические) позволяет просматривать значения нескольких последних переменных, к которым осуществлялся доступ в процессе выполнения программы.

Вкладка Locals (Локальные) позволяет просматривать значения переменных, к которым получается доступ в методе, выполняемом в текущий момент

Вкладка Watch (Слежение) позволяет просматривать значения любых интересующих переменных за счет явного указания их имен непосредственно в окне Watch.

Исключения

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

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

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

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

На самом деле в Visual Studio предлагается очень действенное решение. Если заглянуть в меню Debug (Отладка), то можно будет обнаружить там пункт Exceptions (Исключения). В случае выбора этого пункта открывается диалоговое окно Exceptions (Исключения). Это окно позволяет указывать, что должно происходить при выдаче исключения. Здесь можно указать, что выполнение должно продолжаться или же останавливаться с переходом в режим отладки, в случае чего произойдет останов, а отладчик окажется прямо на самом операторе throw:

Окно Exceptions

Visual Studio известно обо всех классах исключений, которые доступны в базовых классах .NET, и о многих таких исключениях, которых могут выдаваться за пределами среды .NET. Распознавать автоматически специальные классы исключений, создаваемые разработчиками, Visual Studio не умеет, но позволяет вручную добавлять такие классы исключений в список и, следовательно, указывать, какие из таких исключений должны приводить к немедленному прекращению выполнения приложения. Для этого необходимо щелкнуть на кнопке Add (Добавить), которая активизируется при выборе самого верхнего узла в дереве, и ввести имя специального класса исключения.

Дополнительные команды отладки исходного кода

Компиляция практически всего коммерческого программного обеспечения на стадии отладки и на стадии подготовки окончательной версии продукта должна проводиться немного по-разному. Среда Visual Studio способна понимать это, поскольку сохраняет информацию обо всех параметрах, которые ей надлежит передавать компилятору. Для поддержки разных вариантов компоновки проекта Visual Studio потребуется сохранять подобную информацию в более чем одном экземпляре. Разные экземпляры такой информации называются конфигурациями. При создании проекта Visual Studio автоматически предлагает на выбор две таких конфигурации, которые называются, соответственно, Debug (Отладка) и Release (Выпуск):

Конфигурация Debug обычно указывает, что никакие операции по оптимизации выполняться не должны, в исполняемом коде должна присутствовать дополнительная отладочная информация, а компилятор должен предполагать, что в коде определен препроцессорный символ отладки Debug, если только он не был явно отменен с помощью директивы #undefined.

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

Можно также определять собственные конфигурации. Это необходимо, например, для компоновки приложения с несколькими отличающимися версиями. Раньше из-за проблем, связанных с поддержкой кодировки Unicode в Windows NT, но не в Windows 95, для многих проектов на С++ было принято создавать две конфигурации — одну для Unicode, а вторую для (multibyte character set — набор многобайтных символов).

Разбираемся с условными брейкпоинтами в C++

Для большинства случаев дебаггинга вам должно с головой хватать стандартных точек останова (breakpoints или брейкпоинтов). Но иногда кода для проверки, объектов или случаев попросту слишком много. Что делать, если мы хотим отфильтровать код, генерирующий точку останова? Прошу вас поприветствовать условные точки останова!

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

Давайте рассмотрим простой пример: что, если нам нужно отлаживать цикл, который перебирает 100 прямоугольников, и у нас есть подозрение, что прямоугольники большого размера каким-то образом повреждаются в результате обработки в рамках этого цикла? Вы же не будете отлаживать каждую итерацию этого цикла? Вас может еще хватить на первые десять объектов, но перебирать вручную сто или даже тысячу — это безумие! Возможно, мы могли бы установить условную точку останова и работать только с большими прямоугольниками, с которыми у нас возникает проблема.

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

Рисунок 1: Установка точки останова для перехвата интересующих нас прямоугольников

Рисунок 1: Установка точки останова для перехвата интересующих нас прямоугольников

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

Как вы могли заметить, в Visual Studio 2015 значительно улучшили диалоговое окно для добавления условий (Conditions) и действий (Actions). Например, вы можете открыть его и продолжать редактировать код. Ранее (как, например, в Visual Studio 2013, показанном на рисунке 2) все окна для условий (“Filter”, “Condition” и “When Hit”) были стандартными модальными окнами. Более того, само по себе окно намного чище.

Рисунок 2: Для сравнения аналог окна из Visual Studio 2013

Рисунок 2: Для сравнения аналог окна из Visual Studio 2013

Теперь, когда мы знаем, как создавать простые условия, давайте углубимся в более сложные возможности.

Expression Evaluator (встроенное средство вычисления выражений) — мы можем писать код внутри редактора условий, но есть некоторые ограничения: вы не можете вызывать код, который имеет какие-либо побочные эффекты, создает новые переменные, содержит new/delete или макросы. К сожалению, это также означает, что мы не можем вызвать пользовательскую функцию с локальными переменными, поскольку она изменяет текущее состояние приложения.

Но у нас в арсенале есть ряд встроенных функций string (‘ strlen ‘, ‘ strcmp ‘, ‘ strst ‘, . ), system (‘ GetLastError ‘) и некоторые математические функции.

Полный список выражений, которые можно использовать для создания условий, вы найдете в статье MSDN «Выражения в отладчике».

Но это еще не все.

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

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

Счетчик срабатываний (Hit Count): точка останова будет срабатывать после заданного количества итераций, например, каждый пятый раз или только в десятый, или после сотой итераций.

Фильтр (Filter): позволяет фильтровать точки останова на данной машине, процессе или потоке.

Хотя это и не является условием, в окне установки условной точки останова вы можете указать “действие” (action), которое будет выполняться при попадании в точку останова. Например, мы можем вывести значение переменной или текущее состояние стека. Это очень удобно, поскольку означает, что мы можем динамически вставлять трассировочные операторы без перекомпиляции кода.

Условные точки останова дают нам возможность сосредоточиться на конкретных проблемах и случаях. Это особенно важно в ситуациях, когда нужно оперировать огромным количеством объектов или событий. Грамотное использование условных точек останова может сэкономить вам кучу времени.

Код примера можно найти в моем репозитории на GitHub.

Ссылки:

Статья подготовлена в преддверии старта курса «C++ Developer. Professional».

А уже сегодня вечером состоится открытое занятие «ООП глазами C++». Хоть и модно критиковать ООП-подход к разработке кода, он остаётся самым популярным во многих и многих сферах. Поэтому не знать и не уметь использовать данную парадигму разработки для настоящего профессионала просто не вежливо. На вебинаре поговорим и посмотрим на примерах о том, как термины ООП реализуются в синтаксисе языка C++. Регистрируйтесь по ссылке.

Урок №26. Отладка программ: степпинг и точки останова

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

Типы ошибок

Синтаксическая ошибка возникает, когда вы пишете код, который не соответствует правилам грамматики языка C++. Например, пропущенные точки с запятой, необъявленные переменные, непарные круглые или фигурные скобки и т.д. В следующей программе есть несколько синтаксических ошибок:

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

Семантическая ошибка возникает, когда код является синтаксически правильным, но делает не то, что задумал программист.

Иногда это может привести к сбою в программе, например, если делить на ноль:

Либо делать вообще не то, что нужно:

К сожалению, компилятор не ловит подобные ошибки, так как он проверяет только то, что вы написали, а не то, что вы хотели этим сделать.

В примерах, приведенных выше, ошибки довольно легко обнаружить. Но в большинстве программ (в которых больше 40 строк кода), семантические ошибки увидеть с помощью простого просмотра кода будет не так-то и легко.

И здесь нам на помощь приходит отладчик.

Отладчик

Отладчик (или «дебаггер», от англ. «debugger») — это компьютерная программа, которая позволяет программисту контролировать выполнение кода. Например, программист может использовать отладчик для выполнения программы пошагово, последовательно изучая значения переменных в программе.

Старые дебаггеры, такие как GDB, имели интерфейс командной строки, где программисту приходилось вводить специальные команды для старта работы. Современные дебаггеры имеют графический интерфейс, что значительно упрощает работу с ними. Сейчас почти все современные IDE имеют встроенные отладчики. То есть, вы можете использовать одну среду разработки как для написания кода, так и для его отладки (вместо постоянного переключения между разными программами).

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

Примечание: Перед тем как продолжить, убедитесь, что вы находитесь в режиме конфигурации «Debug». Все скриншоты данного урока выполнены в Visual Studio 2019.

Степпинг

Степпинг (англ. «stepping») — это возможность отладчика выполнять код пошагово (строка за строкой). Есть три команды степпинга:

Команда «Шаг с заходом»

Команда «Шаг с обходом»

Команда «Шаг с выходом»

Мы сейчас рассмотрим каждую из этих команд в индивидуальном порядке.

Команда «Шаг с заходом»

Команда «Шаг с заходом» (англ. «Step into») выполняет следующую строку кода. Если этой строкой является вызов функции, то «Шаг с заходом» открывает функцию и выполнение переносится в начало этой функции.

Давайте рассмотрим очень простую программу:

Как вы уже знаете, при запуске программы выполнение начинается с вызова главной функции main(). Так как мы хотим выполнить отладку внутри функции main(), то давайте начнем с использования команды «Шаг с заходом».

В Visual Studio, перейдите в меню «Отладка» > «Шаг с заходом» (либо нажмите F11 ):

Если вы используете другую IDE, то найдите в меню команду «Step Into/Шаг с заходом» и выберите её.

Когда вы это сделаете, должны произойти две вещи. Во-первых, так как наше приложение является консольной программой, то должно открыться консольное окно. Оно будет пустым, так как мы еще ничего не выводили. Во-вторых, вы должны увидеть специальный маркер слева возле открывающей скобки функции main(). В Visual Studio этим маркером является жёлтая стрелочка (если вы используете другую IDE, то должно появиться что-нибудь похожее):

Стрелка-маркер указывает на следующую строку, которая будет выполняться. В этом случае отладчик говорит нам, что следующей строкой, которая будет выполняться, — будет открывающая фигурная скобка функции main(). Выберите «Шаг с заходом» еще раз — стрелка переместится на следующую строку:

Это значит, что следующей строкой, которая будет выполняться, — будет вызов функции printValue(). Выберите «Шаг с заходом» еще раз. Поскольку printValue() — это вызов функции, то мы переместимся в начало функции printValue():

Выберите еще раз «Шаг с заходом» для выполнения открывающей фигурной скобки printValue(). Стрелка будет указывать на std::cout << nValue; .

Теперь выберите «Шаг с обходом» (F10). Вы увидите число 5 в консольном окне.

Выберите «Шаг с заходом» еще раз для выполнения закрывающей фигурной скобки printValue(). Функция printValue() завершит свое выполнение и стрелка переместиться в функцию main(). Обратите внимание, в main() стрелка снова будет указывать на вызов printValue():

Может показаться, будто отладчик намеревается еще раз повторить цикл с функцией printValue(), но в действительности он нам просто сообщает, что он только что вернулся из этой функции.

Выберите «Шаг с заходом» два раза. Готово, все строки кода выполнены. Некоторые дебаггеры автоматически прекращают сеанс отладки в этой точке. Но Visual Studio так не делает, так что если вы используете Visual Studio, то выберите «Отладка» > «Остановить отладку» (или Shift+F5 ):

Таким образом мы полностью остановили сеанс отладки нашей программы.

Команда «Шаг с обходом»

Как и команда «Шаг с заходом», команда «Шаг с обходом» (англ. «Step over») позволяет выполнить следующую строку кода. Только если этой строкой является вызов функции, то «Шаг с обходом» выполнит весь код функции в одно нажатие и возвратит нам контроль после того, как функция будет выполнена.

Примечание для пользователей Code::Blocks: Команда «Step over» называется «Next Line».

Точки останова с условием в Visual Studio. Основы

Visual Studio позволяет устанавливать условия при выполнении которых выполнение программы будет приостановлено и вы перейдёте в отладчик в данной точке останова.

В качестве примера рассмотрим простейшую программу, которая выводит числа от 0 до 10.

Точку останова мы поставим внутри цикла на операторе, который выводит на экран консоли значение переменной i.

Если мы просто поставим точку останова (см. скриншот ниже), то выполнение программы будет приостанавливаться на каждой итерации цикла.

Но, в этом не всегда есть необходимость. Также при большом количестве итераций это как правило увеличивает время отладки потому, что:

  1. Нужная итерация может выполняться не вначале алгоритма и до неё цикл должен выполниться определённое количество раз;
  2. Даже после выполнения нужной итерации точка останова без условия будет приводить к приостановке программы при каждом выполнении цикла до тех пор, пока программа из него не выйдет.

Помимо вышесказанного обычная точка останова может создать немало трудностей и при отладке алгоритмов, не содержащих циклы или ошибок с ними не связанных (во всяком случае напрямую). Например, если ошибка в методе класса воспроизводится только при определённых исходных данных нет смысла переходить в отладчик при каждом его вызове.

Но, как сделать чтобы точка останова срабатывала только при определённых условиях?

Для этого в интерфейсе Visual Studio нужно навести курсор мыши на точку останова. В появившейся рядом панели нажать кнопку со значком в виде шестерни. В открывшемся окне установить флажок «Условие». После этого в правом текстовом поле можно указать условия срабатывания точки останова.

Как это выглядит показано ниже на скриншоте.

Условия для точек останова записываются в виде логического выражения на языке программирования проекта. То есть, если ваш проект на C# (как в нашем примере) условие следует записать на C#. Если на VB.NET, то на VB.NET и т.д.

После нажатия кнопки «Закрыть» параметры точки останова (включая условие) будут сохранены. Для наглядности значок точки останова, для которой заданы специальные параметры, изменит свой вид (см. скриншот).

В данном примере точка останова будет срабатывать при значении переменной I равном 5.

Как это выглядит в отладчике:

Как это выглядит в программе:

Важно отметить, что в данной статье мы разобрали лишь простейший пример.

Условия для точек останова могут быть гораздо сложнее. Также возможности отладчика Visual Studio не ограничиваются только проверкой логических выражений.

На скриншоте ниже показан пример точки останова, которая срабатывает при изменении значения переменной (в данном случае i).

Характер условия точки останова задаётся в среднем поле (см. скриншот) в выпадающем меню. Также это меню определяет какие условия могут быть доступны дополнительно. Но, это уже тема для отдельной статьи.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *