Potwierdzenia C/C++

Instrukcja asercji określa warunek, który ma być spełniony w punkcie programu. Jeśli ten warunek nie jest spełniony, potwierdzenie zakończy się niepowodzeniem, wykonanie programu zostanie przerwane, a zostanie wyświetlone okno dialogowe Asercji nie powiodło się.

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

  • Asercji MFC dla programów MFC.

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

  • Asercji CRT dla programów korzystających z biblioteki języka C w czasie wykonywania.

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

    Za pomocą asercji można przechwytywać błędy logiki, sprawdzać wyniki operacji i testować warunki błędu, które powinny zostać obsłużone.

W tym temacie

Jak działają aseracje

Asercji w kompilacjach debugowania i wydania

Skutki uboczne używania asercji

Asercji CRT

Asercji MFC

Jak działają aseracje

Gdy debuger zatrzyma się z powodu asercji biblioteki MFC lub C w czasie wykonywania, jeśli źródło jest dostępne, debuger przechodzi do punktu w pliku źródłowym, w którym wystąpiła aseracja. Komunikat asercji jest wyświetlany zarówno w oknie Dane wyjściowe, jak i w oknie dialogowym Asercji nie powiodło się . 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

Asercji w kompilacjach debugowania i wydania

Instrukcje asercji kompilowane tylko wtedy, gdy _DEBUG jest zdefiniowane. W przeciwnym razie kompilator traktuje asercji jako instrukcje null. W związku z tym oświadczenia asercji nie nakładają żadnych kosztów związanych z nadmiernym obciążeniem ani wydajnością w ostatnim programie wydań 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 potwierdzenie, które modyfikuje nM wartość:

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

ASSERT Ponieważ wyrażenie nie jest oceniane w wersji wydania programu, nM będą miały różne wartości w wersjach debugowania i wydania. 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łania myFnctn zarówno w wersjach debugowania, jak i wydania, dlatego dopuszczalne jest użycie. Jednak użycie VERIFY wymusza narzut na niepotrzebne wywołanie funkcji w wersji wydania.

W tym temacie

Asercji CRT

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

Makro Result
_ASSERT Jeśli określone wyrażenie zwróci wartość FALSE, nazwa pliku i numer wiersza ._ASSERT
_ASSERTE Taki sam jak _ASSERT, oraz ciąg reprezentujący wyrażenie, które zostało asertywne.

_ASSERTE jest bardziej zaawansowany, ponieważ zgłasza wyrażenie asertywne, które okazało się fałszywe. 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ęć.

Po _DEBUG zdefiniowaniu _ASSERTE makro 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 uszkodzeń stert

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

_ASSERTE(_CrtCheckMemory());

Sprawdzanie ważności wskaźnika

W poniższym przykładzie użyto _CrtIsValidPointer , aby sprawdzić, 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ć wskaźnik wskazujący pamięć w lokalnej stercie (sterta utworzona i zarządzana przez to wystąpienie biblioteki czasu wykonywania języka 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 poza granicami, ale także wskaźniki do zmiennych statycznych, zmiennych stosu i innej nielokanej 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 MFC ASSERT_VALID również metody 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 obiektów debugera, aby określić, dlaczego aseracja nie powiodła się. Jeśli włączono debugowanie just in time, a debuger nie był jeszcze uruchomiony, okno dialogowe może uruchomić debuger.

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 wydania. Jeśli musisz ocenić wyrażenie w wersji wydania, 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łonięcia AssertValid podczas tworzenia klasy z CObjectklasy , możesz zwiększyć niezawodność klasy, wykonując tę czynności. 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 nazwy 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ęcia AssertValid w pliku CMyData wyglądają następująco:

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

CMyDataAssertValid używa mechanizmu do testowania ważności obiektów przechowywanych w składowych danych. Zastąpienie AssertValidCMyData wywołuje ASSERT_VALID makro dla własnej zmiennej składowej m_pDataList.

Testowanie ważności nie kończy się na tym poziomie, ponieważ klasa CObList zastępuje również AssertValidwartość . To zastąpienie wykonuje dodatkowe testy ważności na wewnętrznym stanie 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ększej pracę można również dodać testy ważności dla CPerson obiektów przechowywanych na liście. Można utworzyć klasę CPersonList z CObList klasy i zastąpić AssertValidelement . W przesłonięci wywołasz metodę CObject::AssertValid , a następnie wykonasz iterację po liście, wywołując wywołanie AssertValid każdego CPerson obiektu przechowywanego na liście. Klasa CPerson pokazana na początku tego tematu już zastępuje AssertValidelement .

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

Ograniczenia elementu AssertValid

Wyzwolona asercji wskazuje, że obiekt jest zdecydowanie zły, 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ć asercji na warunku, który musi być spełniony zgodnie z logiką 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ć asercji CRT w następujący sposób:

_ASSERT(numMols >= 0);

Te instrukcje nie robią nic, jeśli program działa poprawnie. Jeśli błąd logiki powoduje numMols , że wartość jest mniejsza niż zero, jednak asercji zatrzymuje wykonywanie programu i wyświetla okno dialogowe Asercji nie powiodło 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 liczone iMols przez musi być zawsze mniejsza lub równa całkowitej liczbie cząsteczek. numMols Inspekcja wizualna pętli nie pokazuje, że będzie to konieczne, więc instrukcja asercji jest używana po pętli do testowania tego warunku.

W tym temacie

Znajdowanie nieobsługiwane błędy

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, powinien zostać obsłużony myErr i zresetowany do zera przed osiągnięciem asercji. Jeśli myErr ma inną wartość, asercji zakończy się niepowodzeniem, program zostanie zatrzymany, a zostanie wyświetlone okno dialogowe Asercji nie powiodło się.

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 usługę będzie nieobsługiwany w końcowym kodzie wydania.

W tym temacie