Udostępnij za pomocą


Asercji C/C++

Instrukcja asercji określa warunek, który oczekujesz, że będzie prawdziwy w danym momencie programu. Jeśli ten warunek nie jest spełniony, asercja zakończy się niepowodzeniem, wykonanie programu zostanie przerwane, a zostanie wyświetlone okno dialogowe Asercja nie powiodła się.

Program Visual Studio obsługuje instrukcje asercji języka C++, które są oparte na następujących konstrukcjach:

  • Asercje MFC dla programów MFC.

  • ATLASSERT dla programów korzystających z ATL.

  • Asercje CRT dla programów korzystających z biblioteki uruchomieniowej C.

  • Funkcja asercyjności ANSI dla innych programów C/C++.

    Za pomocą asercji można wychwytywać błędy logiczne, sprawdzać wyniki operacji i testować sytuacje błędowe, które powinny być obsłużone.

W tym temacie

Jak działają aseracje

Asercje w kompilacjach Debug i Release

Skutki uboczne używania asercji

Twierdzenia CRT

Asercji MFC

Jak działają aseracje

Gdy debuger zakończy działanie z powodu asercji w bibliotece uruchomieniowej MFC lub C, to jeśli źródło jest dostępne, debuger przechodzi do punktu w pliku źródłowym, w którym wystąpiła asercja. Komunikat asercji jest wyświetlany zarówno w oknie Dane wyjściowe, jak i w oknie dialogowym Assertion Failed. Możesz skopiować komunikat asercji z okna Dane wyjściowe do okna tekstowego, jeśli chcesz zapisać go na potrzeby przyszłego odwołania. Okno Dane wyjściowe może również zawierać inne komunikaty o błędach. Dokładnie zbadaj te komunikaty, ponieważ dostarczają wskazówek dotyczących przyczyny niepowodzenia asercji.

Używanie asercji do wykrywania błędów podczas opracowywania. W zasadzie należy użyć jednej asercji dla każdego założenia. Jeśli na przykład przyjęto założenie, że argument nie ma wartości NULL, użyj asercji, aby przetestować to założenie.

W tym temacie

Instrukcje kontrolne w kompilacjach debugowania i wersjach produkcyjnych

Kompilowanie instrukcji asercyjnych następuje tylko wtedy, gdy _DEBUG jest zdefiniowane. W przeciwnym razie kompilator traktuje asercji jako instrukcje null. W związku z tym instrukcje asercji nie nakładają żadnego narzutu ani kosztów wydajności w ostatecznej wersji programu i pozwalają uniknąć używania #ifdef dyrektyw.

Skutki uboczne używania asercji

Po dodaniu asercji do kodu upewnij się, że asercji nie mają skutków ubocznych. Rozważmy na przykład następujące założenie, które modyfikuje wartość nM.

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

ASSERT Wyrażenie nie jest oceniane w wersji Release programu, więc nM mają różne wartości w wersjach Debug i Release. Aby uniknąć tego problemu w MFC, możesz użyć makra VERIFY zamiast ASSERT. VERIFY oblicza wyrażenie we wszystkich wersjach, ale nie sprawdza wyniku w wersji wydania.

Należy szczególnie uważać na używanie wywołań funkcji w instrukcjach asercji, ponieważ ocena funkcji może mieć nieoczekiwane skutki uboczne.

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

VERIFY wywołuje myFnctn zarówno w wersjach debugowania, jak i wydania, dlatego dopuszczalne jest użycie. Jednak użycie VERIFY powoduje narzut związany z niepotrzebnym wywołaniem funkcji w wersji kompilacyjnej.

W tym temacie

Asercji CRT

Plik nagłówka CRTDBG.H definiuje _ASSERT i _ASSERTE makra do sprawdzania asercji.

Macro Wynik
_ASSERT Jeśli określone wyrażenie zwróci wartość FALSE, nazwa pliku i numer wiersza _ASSERT są przekazywane.
_ASSERTE To samo co _ASSERT, plus ciąg reprezentujący wyrażenie, które zostało poddane asercji.

_ASSERTE jest bardziej skuteczny, ponieważ zgłasza wyrażenie asertywne, które okazało się FAŁSZ. Może to wystarczyć, aby zidentyfikować problem bez odwoływania się do kodu źródłowego. Jednak wersja debugowania aplikacji będzie zawierać stałą ciągu dla każdego wyrażenia asertywnego przy użyciu polecenia _ASSERTE. Jeśli używasz wielu _ASSERTE makr, te wyrażenia ciągu zajmują znaczną ilość pamięci. Jeśli okaże się, że jest to problem, użyj polecenia _ASSERT , aby zapisać pamięć.

Gdy _DEBUG jest zdefiniowany, makro _ASSERTE jest definiowane w następujący sposób:

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

Jeśli wyrażenie asertywne daje wartość FALSE, _CrtDbgReport jest wywoływana w celu zgłoszenia niepowodzenia asercji (domyślnie przy użyciu okna dialogowego komunikatu). Jeśli wybierzesz pozycję Ponów próbę w oknie dialogowym komunikatu, _CrtDbgReport zwraca wartość 1 i _CrtDbgBreak wywołuje debuger za pomocą polecenia DebugBreak.

Jeśli musisz tymczasowo wyłączyć wszystkie asercji, użyj _CtrSetReportMode.

Sprawdzanie uszkodzenia stosu

W poniższym przykładzie użyto _CrtCheckMemory do sprawdzenia uszkodzenia stosu:

_ASSERTE(_CrtCheckMemory());

Sprawdzanie ważności wskaźnika

W poniższym przykładzie użyto _CrtIsValidPointer w celu sprawdzenia, czy dany zakres pamięci jest prawidłowy do odczytu lub zapisu.

_ASSERTE(_CrtIsValidPointer( address, size, TRUE );

W poniższym przykładzie użyto _CrtIsValidHeapPointer, aby zweryfikować, czy wskaźnik wskazuje na pamięć w lokalnej stercie (sterta utworzona i zarządzana przez to wystąpienie biblioteki czasu wykonywania C — biblioteka DLL może mieć własne wystąpienie biblioteki, a tym samym własną stertę poza stertą aplikacji). To twierdzenie przechwytuje nie tylko adresy o wartości null lub wykraczające poza dozwolony zakres, ale także wskaźniki do zmiennych statycznych, zmiennych stosu i innej nielokalnej pamięci.

_ASSERTE(_CrtIsValidHeapPointer( myData );

Sprawdzanie bloku pamięci

W poniższym przykładzie użyto _CrtIsMemoryBlock w celu sprawdzenia, czy blok pamięci znajduje się w stercie lokalnym i ma prawidłowy typ bloku.

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

W tym temacie

Asercji MFC

MFC definiuje makro ASSERT na potrzeby sprawdzania asercji . Definiuje również metody MFC ASSERT_VALID i CObject::AssertValid do sprawdzania stanu wewnętrznego obiektu pochodnego CObject.

Jeśli argument makra MFC ASSERT zwróci wartość zero lub fałsz, makro zatrzymuje wykonywanie programu i ostrzega użytkownika. W przeciwnym razie wykonanie będzie kontynuowane.

Gdy potwierdzenie zakończy się niepowodzeniem, w oknie dialogowym komunikatu zostanie wyświetlona nazwa pliku źródłowego i numer wiersza potwierdzenia. W przypadku wybrania opcji Ponów próbę w oknie dialogowym wywołanie elementu AfxDebugBreak powoduje przerwanie wykonywania w debugerze. W tym momencie możesz zbadać stos wywołań i użyć innych narzędzi debugera, aby określić, dlaczego asercja się nie powiodła. Jeśli włączono debugowanie Just-In-Time, a debugger nie był jeszcze uruchomiony, okno dialogowe może uruchomić debugger.

W poniższym przykładzie pokazano, jak użyć ASSERT polecenia , aby sprawdzić wartość zwracaną funkcji:

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

Funkcja ASSERT z funkcją IsKindOf umożliwia sprawdzanie typów argumentów funkcji:

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

Makro ASSERT nie generuje kodu w wersji Release. Jeśli musisz ocenić wyrażenie w wersji Release, użyj makra VERIFY zamiast ASSERT.

MFC ASSERT_VALID i CObject::AssertValid

Metoda CObject::AssertValid umożliwia sprawdzanie stanu wewnętrznego obiektu w czasie wykonywania. Chociaż nie jest wymagane przesłanianie AssertValid przy dziedziczeniu klasy z CObject, możesz zwiększyć niezawodność swojej klasy, wykonując tę czynność. AssertValid należy wykonać asercji na wszystkich zmiennych członkowskich obiektu, aby sprawdzić, czy zawierają prawidłowe wartości. Na przykład należy sprawdzić, czy zmienne składowe wskaźnika nie mają wartości NULL.

W poniższym przykładzie pokazano, jak zadeklarować AssertValid funkcję:

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

Po zastąpieniu AssertValidwywołaj wersję klasy bazowej AssertValid przed wykonaniem własnych testów. Następnie użyj makra ASSERT, aby sprawdzić elementy członkowskie unikatowe dla klasy pochodnej, jak pokazano poniżej:

#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

Jeśli którykolwiek ze zmiennych składowych przechowuje obiekty, możesz użyć ASSERT_VALID makra do przetestowania ich wewnętrznej ważności (jeśli ich klasy zastępują AssertValid).

Rozważmy na przykład klasę , która przechowuje obiekt CMyDataCObList w jednej ze zmiennych składowych. Zmienna CObList , m_DataListprzechowuje kolekcję CPerson obiektów. Skrócona deklaracja CMyData wygląda następująco:

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 ...
};

Przesłonięcie AssertValid w CMyData wygląda następująco:

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

CMyData używa mechanizmu AssertValid do testowania ważności obiektów przechowywanych w członku danych. Nadpisanie AssertValidCMyData wywołuje makro ASSERT_VALID dla zmiennej składowej m_pDataList.

Testowanie ważności nie kończy się na tym poziomie, ponieważ klasa CObList również nadpisuje AssertValid. Nadpisanie to wykonuje dodatkowe testy ważności wewnętrznego stanu listy. W związku z tym test ważności obiektu CMyData prowadzi do dodatkowych testów ważności dla wewnętrznych stanów przechowywanych CObList obiektów listy.

Dzięki większemu nakładowi pracy można również dodać testy sprawdzania poprawności dla CPerson obiektów przechowywanych na liście. Można utworzyć klasę CPersonList z CObList i nadpisać AssertValid. W przesłonięciu wywołasz metodę CObject::AssertValid, a następnie wykonasz iterację po liście, wywołując AssertValid dla każdego obiektu CPerson przechowywanego w liście. Klasa CPerson pokazana na początku tego tematu już zastępuje AssertValid element.

Jest to zaawansowany mechanizm podczas kompilowania na potrzeby debugowania. Po zakończeniu kompilacji na potrzeby wydania mechanizm jest wyłączany automatycznie.

Ograniczenia elementu AssertValid

Wywołana asercja wskazuje, że obiekt jest niewłaściwy, a wykonanie zostanie zatrzymane. Jednak brak potwierdzenia wskazuje tylko, że nie znaleziono żadnego problemu, ale obiekt nie ma gwarancji, że jest dobry.

W tym temacie

Używanie asercji

Przechwytywanie błędów logiki

Możesz ustawić asercję dla warunku, który musi być spełniony zgodnie z logiką twojego programu. Potwierdzenie nie ma wpływu, chyba że wystąpi błąd logiki.

Załóżmy na przykład, że symulujesz cząsteczki gazu w pojemniku, a zmienna numMols reprezentuje całkowitą liczbę cząsteczek. Ta liczba nie może być mniejsza niż zero, dlatego możesz dołączyć następującą instrukcję asercji MFC:

ASSERT(numMols >= 0);

Możesz też dołączyć asercję CRT na przykład:

_ASSERT(numMols >= 0);

Te instrukcje nie robią nic, jeśli program działa poprawnie. Jeśli błąd logiki powoduje, że numMols jest mniejsze niż zero, asercja zatrzymuje wykonywanie programu i wyświetla okno dialogowe „asercja nie powiodła się”.

W tym temacie

Sprawdzanie wyników

Potwierdzenia są cenne w przypadku operacji testowania, których wyniki nie są oczywiste z szybkiej inspekcji wizualnej.

Rozważmy na przykład następujący kod, który aktualizuje zmienną iMols na podstawie zawartości połączonej listy wskazywanej przez :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

Liczba cząsteczek liczona przez iMols musi być zawsze mniejsza lub równa całkowitej liczbie cząsteczek numMols. Inspekcja wizualna pętli nie wskazuje, że tak będzie w każdym przypadku, dlatego po pętli używa się asercji do sprawdzenia tego warunku.

W tym temacie

Znajdowanie nieobsługiwanych błędów

Asercji można używać do testowania warunków błędów w punkcie kodu, w którym powinny zostać obsłużone wszelkie błędy. W poniższym przykładzie rutyna graficzna zwraca kod błędu lub zero dla powodzenia.

myErr = myGraphRoutine(a, b);

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

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

Jeśli kod obsługi błędów działa prawidłowo, błąd powinien być obsłużony i myErr zresetowany do zera przed wykonaniem asercji. Jeśli myErr ma inną wartość, asercja się nie powiedzie, program się zatrzyma, a okno dialogowe Asercja nie powiodła się zostanie wyświetlone.

Instrukcje asercji nie są jednak zastępowane kodem obsługi błędów. Poniższy przykład przedstawia instrukcję asercji, która może prowadzić do problemów w końcowym kodzie wydania:

myErr = myGraphRoutine(a, b);

/* No Code to handle errors */

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

Ten kod opiera się na instrukcji asercji do obsługi warunku błędu. W związku z tym każdy kod błędu zwrócony przez myGraphRoutine będzie nieobsługiwany w końcowym kodzie wydania.

W tym temacie