Найти в Дзене
Road to the programming

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

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

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

Типы ошибок

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

#include <iostream>; // директивы препроцессора не заканчиваются точкой с запятой
int main ( )
{
std : cout < "Hi there; << x; // недействительный оператор (:), незаконченное предложение (пропущено " ) и необъявленная переменная
return 0 // пропущена точка с запятой в конце стейтмента
}

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

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

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

#include <iostream>
int main ( )
{
int a = 10 ;
int b = 0 ;
std :: cout << a << " / " << b << " = " << a / b ; // делить на 0 нельзя
return 0 ;
}

Иногда это может привести к неверным результатам:

#include <iostream>
int main ( )
{
std :: cout << "Hello, word!" ; // орфографическая ошибка
return 0 ;
}

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

#include <iostream>
int add ( int x , int y )
{
return x - y ; // функция должна выполнять сложение, но выполняет вычитание
}
int main ( )
{
std :: cout << add ( 5 , 3 ) ; // должно быть 8, но результат - 2
return 0 ;
}

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

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

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

Отладчик

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

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

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

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

Степпинг

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

  • Команда "Шаг с заходом"
  • Команда "Шаг с обходом"
  • Команда "Шаг с выходом"

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

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

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

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

#include <iostream>
void printValue ( int nValue )
{
std :: cout << nValue ;
}
int main ( )
{
printValue ( 5 ) ;
return 0 ;
}

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

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

-2

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

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

-3

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

-4

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

-5

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

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

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

-6

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

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

-7

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

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

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

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

Рассмотрим пример, используя следующую программу:

#include <iostream>
void printValue ( int nValue )
{
std :: cout << nValue ;
}
int main ( )
{
printValue ( 5 ) ;
return 0 ;
}

Нажмите «Шаг с заходом», чтобы дойти до вызова функции printValue():

-8

Теперь вместо команды «Шаг с заходом» выберите «Шаг с обходом» (или F10):

-9

Отладчик выполнит функцию (которая выведет значение 5 в консоль), а затем возвратит нам управление на строке return 0; . И это всё за одно нажатие.

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

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

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

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

Рассмотрим все тот же пример:

#include <iostream>
void printValue ( int nValue )
{
std :: cout << nValue ;
}
int main ( )
{
printValue ( 5 ) ;
return 0 ;
}

Нажимайте «Шаг с заходом» до тех пор, пока не перейдете к открывающей фигурной скобке функции printValue():

-10

Затем выберите "Отладка" > "Шаг с выходом" (либо Shift+F11 ):

-11

Вы заметите, что значение 5 отобразилось в консольном окне, а отладчик перешел к вызову функции printValue() в main():

-12

Команда «Выполнить до текущей позиции»

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

Но и здесь современные отладчики предлагают еще несколько инструментов для эффективной отладки программ.

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

#include <iostream>
void printValue ( int nValue )
{
std :: cout << nValue ;
}
int main ( )
{
printValue ( 5 ) ;
return 0 ;
}

Поместите курсор на строку std::cout << nValue; внутри функции printValue(), затем щелкните правой кнопкой мыши и выберите "Выполнить до текущей позиции" (либо Ctrl+F10 ):

-13

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

Команда «Продолжить»

Если вы находитесь в середине сеанса отладки вашей программы, то вы можете сообщить отладчику продолжать выполнение кода до тех пор, пока он не дойдет до конца программы (или до следующей контрольной точки). В Visual Studio эта команда называется «Продолжить» (англ. «Continue» ). В других дебаггерах она может иметь название «Run» или «Go».

Возвращаясь к вышеприведенному примеру, мы находимся как раз внутри функции printValue(). Выберите "Отладка" > "Продолжить" (или F5 ):

-14

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

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

Точки останова (англ. «breakpoint ) — это специальные маркеры, на которых отладчик останавливает процесс выполнения программы.

Чтобы задать точку останова в Visual Studio, щелкните правой кнопкой мыши по выбранной строке > "Точка останова" > "Вставить точку останова" :

-15

Появится кружочек возле строки:

-16

В программе, приведенной выше, создайте точку останова на строке std::cout << nValue; . Затем выберите «Шаг с заходом» для старта сеанса отладки, а затем «Продолжить». Вы увидите, что вместо завершения выполнения программы и остановки сеанса отладки, отладчик остановится в указанной вами точке:

-17

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