6.5.3. Помощь в отладке

Для условного выполнения отладочного кода программисты С++ иногда используют подход, подобный защите заголовка (см. раздел 2.6.3). Идея в том, что программа будет содержать отладочный код, который выполняется только во время разработки программы. Когда приложение закончено и готово к выпуску, отладочный код исключается. Этот подход подразумевает использование двух средств препроцессора: assert и NDEBUG.

Макрос препроцессора assert

Макросassert — это макрос препроцессора (preprocessor macro). Макрос препроцессора — это переменная препроцессора, действующая как встраиваемая функция. Макрос assert получает одно выражение и использует его как условие:

assert(выражение);

Если результат выражения ложь (т.е. нуль), то макрос assert выдает сообщение и закрывает программу. Если результат выражения — истина (т.е. он отличен от нуля), то макрос assert не делает ничего.

Действие макроса препроцессора подобно вызову функции. Макрос assert получает одно выражение, которое он использует как условие.

Макрос assert определен в заголовке cassert. Как уже упоминалось, относящиеся к препроцессору имена обрабатывает препроцессор, а не компилятор (см. раздел 2.3.2). В результате такие имена можно использовать непосредственно, без объявления using. Таким образом, используется имя assert, а не std::assert, кроме того, для него не предоставляется объявление using.

Макрос assert зачастую используется для проверки "недопустимых" условий. Например, программа обработки вводимого текста могла бы проверять, что все вводимые слова длиннее некоего порогового значения. Эта программа могла бы содержать такой оператор:

assert(word.size() > threshold);

Переменная препроцессора NDEBUG

Поведение макроса assert зависит от состояния переменной препроцессора NDEBUG. Если переменная NDEBUG определена, макрос assert ничего не делает. По умолчанию переменная NDEBUG не определена, поэтому по умолчанию макрос assert выполняет проверку.

Отладку можно "выключить", предоставив директиву #define, определяющую переменную NDEBUG. В качестве альтернативы большинство компиляторов предоставляет параметр командной строки, позволяющий определять переменные препроцессора:

$ CC -D NDEBUG main.С # use /D with the Microsoft compiler

Результат будет тот же, что и при наличии строки #define NDEBUG в начале файла main.С.

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

В дополнение к макросу assert можно написать собственный отладочный код, выполняющийся в зависимости от переменной NDEBUG. Если переменная NDEBUG не определена, код между директивами #ifndef и #endif выполняется, а в противном случае игнорируется:

void print(const int ia[], size_t size) {

#ifndef NDEBUG

// __func__ - локальная статическая переменная, определенная

// компилятором. Она содержит имя функции

cerr << __func__ << ": array size is " << size << endl;

#endif

// ...

Здесь переменная __func__ используется для вывода имени отлаживаемой функции. Компилятор определяет переменную __func__ в каждой функции. Это локальный статический массив типа const char, содержащий имя функции.

Кроме переменной __func__, определяемой компилятором С++, препроцессор определяет четыре других имени, которые также могут пригодиться при отладке:

__FILE__ строковый литерал, содержащий имя файла.

__LINE__ целочисленный литерал, содержащий номер текущий строки.

__TIME__ строковый литерал, содержащий файл и время компиляции.

__DATE__ строковый литерал, содержащий файл и дату компиляции.

Эти константы можно использовать для отображения дополнительной информации в сообщениях об ошибках:

if (word.size() < threshold)

 cerr << "Error: " << __FILE__

      << " : in function " << __func__

      << " at line " << __LINE__ << endl

      << " Compiled on " << __DATE__

      << " at " << __TIME__ << endl

      << " Word read was "" << word << "": Length too short" << endl;

Если передать этой программе строку, которая короче threshold, то будет создано следующее сообщение об ошибке:

Error: wdebug.cc : in function main at line 27

       Compiled on Jul 11 2012 at 20:50:03

       Word read was "foo": Length too short

Упражнения раздела 6.5.3

Упражнение 6.47. Пересмотрите программу, написанную в упражнении раздела 6.3.2, где использовалась рекурсия для отображения содержимого вектора так, чтобы условно отображать информацию о ее выполнении. Например, отобразите размер вектора при каждом вызове. Откомпилируйте и запустите программу с включенной отладкой и с выключенной.

Упражнение 6.48. Объясните, что делает этот цикл и стоит ли использовать в нем макрос assert:

string s;

while (cin >> s && s != sought) { } // пустое тело

assert(cin);

Более 800 000 книг и аудиокниг! 📚

Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением

ПОЛУЧИТЬ ПОДАРОК