Утверждения C/C++

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

Visual Studio поддерживает операторы утверждений С++, основанные на следующих конструкциях:

  • Утверждения MFC для программ MFC.

  • ATLASSERT для программ, использующих ATL.

  • Утверждения CRT для программ, использующих библиотеку времени выполнения C.

  • Функция assert ANSI для других программ C/C++.

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

В этом разделе

Как работают утверждения

Утверждения в сборках отладки и выпуска

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

Утверждения CRT

Утверждения MFC

Как работают утверждения

Когда отладчик останавливается из-за утверждения MFC или библиотеки времени выполнения C, при наличии исходного кода отладчик перемещается к той точке в исходном файле, где сработало утверждение. Это сообщение утверждения появляется как в окне "Вывод", так и в диалоговом окне Сбой утверждения. Это сообщение можно скопировать из окна Вывод в текстовое окно, если нужно сохранить его для дальнейшего использования. Окно вывода также может содержать другие сообщения об ошибках. Следует тщательно проверять эти сообщения, потому что они могут объяснить причину сбоя утверждения.

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

В этом разделе

Утверждения в сборках отладки и выпуска

Операторы утверждений компилируются, только если определен _DEBUG. В противном случае компилятор рассматривает утверждения как операторы со значением NULL. Поэтому операторы утверждения не накладывают дополнительной нагрузки и не вызывают снижения производительности в итоговом выпуске программы и позволяют избежать использования директив #ifdef.

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

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

ASSERT(nM++ > 0); // Don't do this!

Поскольку выражение ASSERT не оценивается в окончательной версии программы, nM будет иметь различные значения в отладочной версии и в окончательной версии. Чтобы избежать этой проблемы в MFC, можно использовать макрос VERIFY вместо ASSERT. VERIFY оценивает выражение во всех версиях, но не проверяет результат в версии выпуска.

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

ASSERT ( myFnctn(0)==1 ) // unsafe if myFnctn has side effects
VERIFY ( myFnctn(0)==1 ) // safe

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

В этом разделе

Утверждения CRT

Файл CRTDBG.H заголовка определяет _ASSERT и _ASSERTE макросы для утверждения проверка.

Макрос Результат
_ASSERT Если заданное выражение оценивается как FALSE, то результатом будет имя файла и номер строки _ASSERT.
_ASSERTE Так же, как и _ASSERT, плюс строковое представление выражения, для которого было добавлено утверждение.

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

Если _DEBUG определен, макрос _ASSERTE определяется следующим образом:

#define _ASSERTE(expr) \
    do { \
        if (!(expr) && (1 == _CrtDbgReport( \
            _CRT_ASSERT, __FILE__, __LINE__, #expr))) \
            _CrtDbgBreak(); \
    } while (0)

Если утверждаемое выражение оценивается как FALSE, для отчета о сбое утверждения вызывается функция _CrtDbgReport (использующая по умолчанию диалоговое окно сообщения). Если в диалоговом окне сообщения выбирается Повторить, _CrtDbgReport возвращает 1, а _CrtDbgBreak вызывает отладчик с помощью DebugBreak.

Если необходимо временно отключить все утверждения, используйте _CtrSetReportMode.

Проверка целостности кучи

В следующем примере для проверки целостности кучи используется _CrtCheckMemory:

_ASSERTE(_CrtCheckMemory());

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

В следующем примере _CrtIsValidPointer используется для проверки, допустима ли данная область памяти для записи или чтения.

_ASSERTE(_CrtIsValidPointer( address, size, TRUE );

А в этом примере используется _CrtIsValidHeapPointer для проверки, указывает ли указатель на память в локальной куче (которая создается и управляется экземпляром библиотеки времени выполнения C — DLL-файл может иметь свой собственный экземпляр библиотеки и, таким образом, свою собственную кучу, вне кучи приложения). Это утверждение перехватывает не только пустые или выходящие за пределы области адреса, но и указатели на статические переменные, переменные стека и другую нелокальную память.

_ASSERTE(_CrtIsValidHeapPointer( myData );

Проверка блока памяти

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

_ASSERTE(_CrtIsMemoryBlock (myData, size, &requestNumber, &filename, &linenumber));

В этом разделе

Утверждения MFC

Для проверки утверждения MFC определяет макрос ASSERT. Здесь также определяются методы MFC ASSERT_VALID и CObject::AssertValid для проверки внутреннего состояния объекта, производного от CObject.

Если аргумент MFC-макроса ASSERT равняется нулю или значению false, макрос останавливает выполнение программы и предупреждает пользователя; в противном случае выполнение продолжается.

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

Следующий пример показывает, как использовать ASSERT для проверки значения, возвращаемого следующей функцией:

int x = SomeFunc(y);
ASSERT(x >= 0);   //  Assertion fails if x is negative

ASSERT можно применять с функцией IsKindOf, чтобы задать проверку типов аргументов функции:

ASSERT( pObject1->IsKindOf( RUNTIME_CLASS( CPerson ) ) );

Макрос ASSERT не создает никакого кода в окончательной версии. Если именно в окончательной версии нужно оценить выражение, вместо ASSERT следует применять макрос VERIFY.

MFC ASSERT_VALID и CObject::AssertValid

Функция-член CObject::AssertValid периодически проверяет внутреннее состояние объекта во время выполнения. Переопределение функции AssertValid при получении класса из CObject позволяет сделать данный класс более надежным, хотя такое переопределение необязательно. Функция AssertValid должна выполнить утверждения для всех переменных-членов объекта, чтобы подтвердить наличие только допустимых значений. Например, с ее помощью следует проверить, что переменные-члены указателя не равны NULL.

В следующем примере показан способ объявления функции AssertValid:

class CPerson : public CObject
{
protected:
    CString m_strName;
    float   m_salary;
public:
#ifdef _DEBUG
    // Override
    virtual void AssertValid() const;
#endif
    // ...
};

При переопределении AssertValid перед выполнением собственных проверок вызовите версию AssertValid базового класса. Затем примените макрос ASSERT для проверки членов, уникальных для данного производного класса, как показано ниже:

#ifdef _DEBUG
void CPerson::AssertValid() const
{
    // Call inherited AssertValid first.
    CObject::AssertValid();

    // Check CPerson members...
    // Must have a name.
    ASSERT( !m_strName.IsEmpty());
    // Must have an income.
    ASSERT( m_salary > 0 );
}
#endif

Если какие-то из переменных-членов содержат объекты, можно применить макрос ASSERT_VALID для проверки их внутренней допустимости (если их классы переопределяют AssertValid).

Например, рассмотрим класс CMyData, содержащий CObList в одной из своих переменных-членов. В переменной CObList, m_DataList, хранится коллекция объектов CPerson. Сокращенное объявление CMyData выглядит следующим образом:

class CMyData : public CObject
{
    // Constructor and other members ...
    protected:
        CObList* m_pDataList;
    // Other declarations ...
    public:
#ifdef _DEBUG
        // Override:
        virtual void AssertValid( ) const;
#endif
    // And so on ...
};

Переопределение AssertValid в CMyData выглядит следующим образом:

#ifdef _DEBUG
void CMyData::AssertValid( ) const
{
    // Call inherited AssertValid.
    CObject::AssertValid( );
    // Check validity of CMyData members.
    ASSERT_VALID( m_pDataList );
    // ...
}
#endif

CMyData использует механизм AssertValid для проверки действительности объектов, хранящихся в его элементе данных. Метод переопределения AssertValid из CMyData вызывает макрос ASSERT_VALID для собственной переменной-члена m_pDataList.

Проверка допустимости на этом уровне не останавливается, поскольку класс CObList тоже переопределяет AssertValid. Это переопределение выполняет добавочную проверку допустимости внутреннего состояния списка. Таким образом, тест допустимости для объекта CMyData вызывает дополнительную проверку внутреннего состояния сохраненного объекта списка CObList.

Приложив еще немного усилий, точно так же можно добавить тесты допустимости для объектов CPerson, хранящихся в списке. Можно создать класс CPersonList из CObList и переопределить AssertValid. В переопределении можно вызвать CObject::AssertValid и затем итерировать все элементы списка, вызывая AssertValid по каждому объекту CPerson в списке. Класс CPerson, показанный в начале этого раздела, содержит уже переопределенный AssertValid.

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

Ограничения AssertValid

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

В этом разделе

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

Перехват логических ошибок

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

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

ASSERT(numMols >= 0);

Или же вставить подобное CRT-утверждение:

_ASSERT(numMols >= 0);

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

В этом разделе

Проверка результатов

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

Рассмотрим следующий код, в котором для обновления переменной iMols используется содержимое связанного списка, на который указывает mols:

/* This code assumes that type has overloaded the != operator
 with const char *
It also assumes that H2O is somewhere in that linked list.
Otherwise we'll get an access violation... */
while (mols->type != "H2O")
{
    iMols += mols->num;
    mols = mols->next;
}
ASSERT(iMols<=numMols); // MFC version
_ASSERT(iMols<=numMols); // CRT version

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

В этом разделе

Поиск необработанных ошибок

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

myErr = myGraphRoutine(a, b);

/* Code to handle errors and
   reset myErr if successful */

ASSERT(!myErr); -- MFC version
_ASSERT(!myErr); -- CRT version

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

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

myErr = myGraphRoutine(a, b);

/* No Code to handle errors */

ASSERT(!myErr); // Don't do this!
_ASSERT(!myErr); // Don't do this, either!

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

В этом разделе