Поделиться через


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

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

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

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

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

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

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

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

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

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

Ассерции в сборках Debug и Release

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

Утверждения 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 для проверки утверждения.

Macro Result
_ASSERT Если указанное выражение оценивается как FALSE, будут указаны имя файла и номер строки _ASSERT объекта.
_ASSERTE То же самое, что _ASSERT, плюс строковое представление выражения, которое было утверждено.

_ASSERTE более мощный, потому что сообщает об утверждённом выражении, которое оказалось ложным. Это может быть достаточно, чтобы определить проблему без ссылки на исходный код. Однако версия отладки приложения будет содержать строковую константу для каждого выражения, утверждаемого с помощью _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 может иметь собственный экземпляр библиотеки и, следовательно, собственную кучу, помимо кучи приложения). Это утверждение перехватывает не только адреса со значением NULL или вне границ, но также указатели на статические переменные, переменные стека и любую другую нелокальную память.

_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

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

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

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

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 в одной из его переменных-членов. Переменная CObListm_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 ...
};

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

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

CMyData AssertValid использует механизм для проверки допустимости объектов, хранящихся в его элементе данных. Переопределение AssertValidCMyData вызывает 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 , будет необработанным в окончательном коде выпуска.

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