Отладка приложений

       

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


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

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

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

Первое правило: в одном операторе утверждения следует проверять только один элемент. Если в одном утверждении проверяется несколько условий, то нет никакой возможности узнать, какое из условий явилось причиной неудачи всего утверждения. В следующем примере показано два способа проверки одной и той же функции с помощью оператора ASSERT. Хотя утверждение в первой функции и будет отлавливать неправильный параметр, оно не сообщит, какое условие привело к неудаче (т. е. останется неизвестным, какой из трех параметров является неправильным).

// Неправильный способ записи утверждения. Какой параметр был

// неправильным?


BOOL GetPathltem ( int i, LPTSTR szltem, int iLen)

{

ASSERT ( ( i > 0 ) &&

( NULL != szltem ) && 

( ( iLen > 0) && ( iLen < MAX_PATH) ) && 

( FALSE = IsBadWriteStringPtr ( szltem, iLen)));

}

// Подходящий способ. Каждый параметр проверяется

// индивидуально, так что вы можете видеть, какой из них неправильный.

BOOL GetPathltem ( int i, LPTSTR szltem, int iLen)

{

ASSERT ( i > 0);

ASSERT ( NULL != szltem);

ASSERT ( ( iLen > 0) && ( iLen < MAX_PATH));


ASSERT ( FALSE == IsBadWriteStringPtr ( szltem, iLen));

}

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

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

// Пример проверки только части ошибочного условия

BOOL EnumerateListltems ( PFNELCALLBACK pfnCallback)

{

ASSERT ( NULL != pfnCallback);

}

Для полной проверки корректности указателя, в операторе ASSERT можно также использовать API-функцию isBadCodePtr:

// Пример полной проверки ошибочного условия

BOOL EnumerateListltems (PFNELCALLBACK pfnCallback)

{

ASSERT ( FALSE = IsBadCodePtr ( pfnCallback));

}

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

 Пример неправильного определения аргумента оператора утверждения: 

 отрицательное значение nCount не будет обнаружено функцией утверждения.

 Function UpdateListEntries(ByVal nCount As Integer) as Integer

Debug.Assert nCount

.

.

.

End Function

' Здесь аргумент вызова определен так (nCount>0) , что Assert-функция 

' правильно отреагирует на отрицательное значение nCount 

Function UpdateListEntries(ByVal nCount As Integer) as Integer

 Debug.Assert nCount > 0

.

.

.

End Function

В первом примере, по существу, проверяются все значения nCount, не равные нулю, так что неправильные значения параметра (nCount <= 0) не будут обнаружены функцией Assert. Во втором условие проверки сформулировано точнее (nCount > 0), так что утверждение, во-первых, оказывается самодокументированным (т.


к. в выражении nCount > 0 явно указано условие проверки) и, во-вторых, функция утверждения должным образом реагирует на все "неправильные" (<= 0) значения параметра nCount.

 Когда функция UpdateListEntries получает в качестве параметра неположительное значение, то функция-утверждение Debug.Assert обнаруживает этот факт при оценке выражения nCount > 0 и выводит на экран панель сообщений ASSERTION FAILURE. — Пер.

В языках С и C++ имеются специальные проверочные функции, приведенные в табл. 3.1 и помогающие создавать достаточно описательные утверждения. Эти функции можно вызывать и из программ на Visual Basic, но делать этого не нужно из-за проблем с указателями.

Таблица 3.1. Вспомогательные функции для создания описательных утверждений в C/C++

Функция

Описание

GetOb j ectType

Функция подсистемы интерфейса графических устройств (GDI), которая возвращает тип GDI дескриптора

IsBadCodePtr

Проверяет правильность указателя памяти

IsBadReadPtr

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

IsBadStringPt

Проверяет, может ли строчный указатель читать символы до NULL-терминатора (или указанное максимальное число символов)

IsBadWritePtr

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

IsWindow

Проверяет, является ли HWND-параметр правильным окном

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

Visual Basic имеет свой набор функций, помогающих проверять достоверность специфических условий Visual Basic. Все эти функции перечислены в табл 3.2. Если разработчик, следуя общепринятой практике программирования на языке Visual Basic, не использует спецификатор variants и явно определяет спецификаторы ByVal и ByRef для параметров, то ему нет необходимости так часто проверять достоверность типов переменных. Если же вы не придерживаетесь подобной практики, то, по крайней мере, получаете некоторый набор хороших средств для выполнения такой проверки.

Таблица 3.2. Справочные функции для описательных утверждений Visual Basic



Содержание раздела