Freigeben über


C/C++-Assertionen

Eine Assertionsanweisung gibt eine Bedingung an, die sie zu einem Zeitpunkt in Ihrem Programm als wahr erwarten. Wenn diese Bedingung nicht zutrifft, schlägt die Assertion fehl, die Ausführung des Programms wird unterbrochen, und das Dialogfeld "Assertion fehlgeschlagen" wird angezeigt.

Visual Studio unterstützt C++-Assertionsanweisungen, die auf den folgenden Konstrukten basieren:

  • MFC-Assertionen für MFC-Programme.

  • ATLASSERT für Programme, die ATL verwenden.

  • CRT-Assertionen für Programme, die die C-Laufzeitbibliothek verwenden.

  • Die ANSI-Assertionsfunktion für andere C/C++-Programme.

    Sie können Assertionen verwenden, um Logikfehler abzufangen, Ergebnisse eines Vorgangs zu überprüfen und Fehlerbedingungen zu testen, die behandelt werden sollen.

Inhalt

Funktionsweise von Assertionen

Assertions in Debug- und Release-Builds

Nebenwirkungen der Verwendung von Assertionen

CRT-Assertionen

MFC-Assertionen

Funktionsweise von Assertionen

Wenn der Debugger aufgrund einer MFC- oder C-Laufzeitbibliotheks-Assertion angehalten wird, navigiert der Debugger, wenn die Quelle verfügbar ist, zu dem Punkt in der Quelldatei, an dem die Assertion aufgetreten ist. Die Assertionsmeldung wird sowohl im Ausgabefenster als auch im Dialogfeld Assertionsfehler angezeigt. Sie können die Assertionsnachricht aus dem Ausgabefenster in ein Textfenster kopieren, wenn Sie sie für zukünftige Verweise speichern möchten. Das Ausgabefenster enthält möglicherweise auch andere Fehlermeldungen. Überprüfen Sie diese Nachrichten sorgfältig, da sie Hinweise auf die Ursache des Assertionsfehlers liefern.

Verwenden Sie Assertionen, um Fehler während der Entwicklung zu erkennen. Verwenden Sie in der Regel eine Assertion für jede Annahme. Wenn Sie beispielsweise davon ausgehen, dass ein Argument nicht NULL ist, verwenden Sie eine Assertion, um diese Annahme zu testen.

In diesem Thema

Assertions in Debug- und Release-Builds

Assertionen werden nur kompiliert, wenn _DEBUG definiert ist. Andernfalls behandelt der Compiler Assertionen als NULL-Anweisungen. Daher erzwingen Assertionsanweisungen keinen Mehraufwand oder keine Leistungskosten in Ihrem endgültigen Release-Programm und ermöglichen es Ihnen, die Verwendung von #ifdef Direktiven zu vermeiden.

Nebenwirkungen der Verwendung von Assertionen

Wenn Sie Ihrem Code Assertionen hinzufügen, stellen Sie sicher, dass die Assertionen keine Nebenwirkungen haben. Betrachten Sie beispielsweise die folgende Assertion, die den nM Wert ändert:

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

Da der ASSERT Ausdruck in der Releaseversion Ihres Programms nicht ausgewertet wird, nM weisen sie unterschiedliche Werte in den Debug- und Releaseversionen auf. Um dieses Problem in MFC zu vermeiden, können Sie das VERIFY-Makro anstelle von ASSERT. VERIFY Wertet den Ausdruck in allen Versionen aus, überprüft aber nicht das Ergebnis in der Releaseversion.

Achten Sie besonders darauf, Funktionsaufrufe in Assertionsanweisungen zu verwenden, da die Auswertung einer Funktion unerwartete Nebenwirkungen haben kann.

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

VERIFY ruft myFnctn sowohl in der Debug- als auch in der Release-Version auf, daher ist es zulässig zu verwenden. Die Verwendung VERIFY setzt jedoch den Aufwand eines unnötigen Funktionsaufrufs in der Releaseversion auf.

In diesem Thema

CRT-Assertionen

Die CRTDBG.H Headerdatei definiert die _ASSERT Und _ASSERTE Makros für die Assertionsprüfung .

Makro Ergebnis
_ASSERT Wenn der angegebene Ausdruck als FALSCH ausgewertet wird, werden der Dateiname und die Zeilennummer von _ASSERT angegeben.
_ASSERTE Identisch mit _ASSERT, plus einer Zeichenfolgendarstellung des Ausdrucks, der bestätigt wurde.

_ASSERTE ist leistungsstärker, da es den angegebenen Ausdruck meldet, der sich als falsch erwiesen hat. Dies reicht möglicherweise aus, um das Problem zu identifizieren, ohne auf den Quellcode zu verweisen. Die Debugversion Ihrer Anwendung enthält jedoch eine Zeichenfolgenkonstante für jeden Ausdruck, der mit _ASSERTE überprüft wird. Wenn Sie viele _ASSERTE Makros verwenden, belegen diese Zeichenfolgenausdrücke einen erheblichen Arbeitsspeicher. Wenn sich dies als Problem erweist, verwenden Sie _ASSERT zum Speichern von Arbeitsspeicher.

Wenn _DEBUG definiert ist, wird das Makro _ASSERTE wie folgt definiert:

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

Wenn der bestätigten Ausdruck als FALSCH ausgewertet wird, wird _CrtDbgReport aufgerufen, um den Assertionsfehler zu melden (standardmäßig mithilfe eines Meldungsdialogfelds). Wenn Sie im Meldungsdialogfeld die Option "Wiederholen " auswählen, _CrtDbgReport wird 1 zurückgegeben und _CrtDbgBreak der Debugger durch DebugBreakaufgerufen.

Wenn Sie alle Assertionen vorübergehend deaktivieren müssen, verwenden Sie _CtrSetReportMode.

Überprüfung auf Heap-Korruption

Im folgenden Beispiel wird _CrtCheckMemory verwendet, um die Beschädigung des Heaps zu überprüfen:

_ASSERTE(_CrtCheckMemory());

Überprüfen der Gültigkeit des Zeigers

Im folgenden Beispiel wird _CrtIsValidPointer verwendet, um zu überprüfen, ob ein bestimmter Speicherbereich zum Lesen oder Schreiben gültig ist.

_ASSERTE(_CrtIsValidPointer( address, size, TRUE );

Im folgenden Beispiel wird _CrtIsValidHeapPointer verwendet, um zu überprüfen, ob ein Zeiger auf den Speicher im lokalen Heap verweist (der heap, der von dieser Instanz der C-Laufzeitbibliothek erstellt und verwaltet wird. Eine DLL kann eine eigene Instanz der Bibliothek und daher einen eigenen Heap außerhalb des Anwendungshaps haben). Diese Assertion fängt nicht nur NULL- oder außergebundene Adressen auf, sondern auch Zeiger auf statische Variablen, Stapelvariablen und andere nicht lokale Speicher.

_ASSERTE(_CrtIsValidHeapPointer( myData );

Überprüfen eines Speicherblocks

Im folgenden Beispiel wird _CrtIsMemoryBlock verwendet, um zu überprüfen, ob sich ein Speicherblock im lokalen Heap befindet und über einen gültigen Blocktyp verfügt.

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

In diesem Thema

MFC-Assertionen

MFC definiert das ASSERT-Makro für die Assertionsprüfung. Außerdem definiert es die MFC ASSERT_VALID- und CObject::AssertValid-Methoden zum Überprüfen des internen Zustands eines CObject-abgeleiteten Objekts.

Wenn das Argument des MFC-Makros ASSERT als null oder falsch ausgewertet wird, hält das Makro die Programmausführung an und warnt den Benutzer. Andernfalls wird die Ausführung fortgesetzt.

Wenn eine Assertion fehlschlägt, zeigt ein Meldungsdialogfeld den Namen der Quelldatei und die Zeilennummer der Assertion an. Wenn Sie im Dialogfeld "Wiederholen" auswählen, führt ein Aufruf von AfxDebugBreak dazu, dass die Ausführung für den Debugger abgebrochen wird. An diesem Punkt können Sie den Aufrufstapel untersuchen und andere Debuggereinrichtungen verwenden, um zu bestimmen, warum die Assertion fehlgeschlagen ist. Wenn Sie das Just-in-Time-Debugging aktiviert haben und der Debugger noch nicht ausgeführt wurde, kann das Dialogfeld den Debugger starten.

Das folgende Beispiel zeigt, wie Sie ASSERT verwenden, um den Rückgabewert einer Funktion zu prüfen:

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

Sie können ASSERT mit der IsKindOf-Funktion verwenden, um die Typüberprüfung von Funktionsargumenten bereitzustellen:

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

Das ASSERT Makro erzeugt keinen Code in der Releaseversion. Wenn Sie den Ausdruck in der Releaseversion auswerten müssen, verwenden Sie das VERIFY-Makro anstelle von ASSERT.

MFC ASSERT_VALID und CObject::AssertValid

Die CObject::AssertValid-Methode stellt Laufzeitüberprüfungen des internen Zustands eines Objekts bereit. Obwohl Sie AssertValid nicht überschreiben müssen, wenn Sie Ihre Klasse von CObject ableiten, können Sie Ihre Klasse zuverlässiger machen, indem Sie dies tun. AssertValid sollte Assertionen für alle Membervariablen des Objekts ausführen, um zu überprüfen, ob sie gültige Werte enthalten. Beispielsweise sollte überprüft werden, ob Zeigermembervariablen nicht NULL sind.

Das folgende Beispiel zeigt, wie eine AssertValid Funktion deklariert wird:

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

Rufen Sie beim Überschreiben von AssertValid die Version der Basisklasse von AssertValid auf, bevor Sie Ihre eigenen Prüfungen durchführen. Verwenden Sie dann das ASSERT-Makro, um die elemente zu überprüfen, die für Ihre abgeleitete Klasse eindeutig sind, wie hier gezeigt:

#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

Wenn eines ihrer Membervariablen Objekte speichert, können Sie das ASSERT_VALID Makro verwenden, um die interne Gültigkeit zu testen (wenn die Klassen außer Kraft setzen AssertValid).

Ziehen Sie beispielsweise eine Klasse CMyDatain Betracht, die eine CObList in einer ihrer Membervariablen speichert. Die CObList Variable , m_DataListspeichert eine Auflistung von CPerson Objekten. Eine gekürzte Deklaration sieht CMyData wie folgt aus:

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

Die AssertValid-Überschreibung in CMyData sieht folgendermaßen aus:

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

CMyData verwendet den AssertValid Mechanismus, um die Gültigkeit der Objekte zu testen, die in ihrem Datenelement gespeichert sind. Die Überschreibung AssertValid von CMyData ruft das ASSERT_VALID-Makro für die eigene m_pDataList-Membervariable auf.

Gültigkeitstests werden auf dieser Ebene nicht beendet, da die Klasse CObList auch außer Kraft setzt AssertValid. Diese Außerkraftsetzung führt zusätzliche Gültigkeitstests für den internen Status der Liste durch. Daher führt ein Gültigkeitstest auf ein CMyData Objekt zu zusätzlichen Gültigkeitstests für die internen Zustände des gespeicherten CObList Listenobjekts.

Mit einigen weiteren Arbeiten könnten Sie auch Gültigkeitstests für die CPerson in der Liste gespeicherten Objekte hinzufügen. Sie könnten eine Klasse CPersonList von CObList ableiten und AssertValid überschreiben. In der Überschreibung rufen Sie zuerst CObject::AssertValid auf und durchlaufen Sie dann die Liste, indem Sie für jedes in der Liste gespeicherte CPerson-Objekt AssertValid aufrufen. Die CPerson Klasse, die am Anfang dieses Themas gezeigt wird, überschreibt AssertValid bereits.

Dies ist ein leistungsstarker Mechanismus, wenn Sie für das Debuggen erstellen. Wenn Sie anschließend für den Release bauen, wird der Mechanismus automatisch deaktiviert.

Einschränkungen von AssertValid

Eine ausgelöste Assertion gibt an, dass das Objekt definitiv schlecht ist und die Ausführung beendet wird. Ein Mangel an Assertion weist jedoch nur darauf hin, dass kein Problem gefunden wurde, aber das Objekt ist nicht garantiert gut.

In diesem Thema

Verwenden von Assertionen

Abfangen von Logikfehlern

Sie können eine Assertion für eine Bedingung festlegen, die gemäß der Logik Ihres Programms wahr sein muss. Die Assertion hat keine Auswirkung, es sei denn, ein Logikfehler tritt auf.

Angenommen, Sie simulieren Gasmoleküle in einem Behälter, und die Variable numMols stellt die Gesamtanzahl der Moleküle dar. Diese Zahl darf nicht kleiner als 0 sein, daher können Sie eine MFC-Assertionsanweisung wie folgt einschließen:

ASSERT(numMols >= 0);

Oder Sie können eine CRT-Assertion wie folgt einschließen:

_ASSERT(numMols >= 0);

Diese Anweisungen tun nichts, wenn Ihr Programm ordnungsgemäß funktioniert. Wenn ein Logikfehler dazu führt, dass numMols kleiner als 0 ist, hält die Assertion die Ausführung Ihres Programms an und zeigt das Dialogfeld "Assertion fehlgeschlagen" an.

In diesem Thema

Überprüfen der Ergebnisse

Assertionen sind für Testvorgänge nützlich, deren Ergebnisse bei einer schnellen visuellen Inspektion nicht offensichtlich sind.

Betrachten Sie beispielsweise den folgenden Code, der die Variable iMols basierend auf dem Inhalt der verknüpften Liste aktualisiert, auf den verwiesen wird: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

Die von iMols gezählte Anzahl von Molekülen muss immer kleiner oder gleich der Gesamtzahl der Moleküle numMols sein. Die visuelle Überprüfung der Schleife zeigt nicht, dass dies notwendigerweise der Fall ist, sodass eine Assertionsprüfung nach der Schleife verwendet wird, um diese Bedingung zu überprüfen.

In diesem Thema

Auffinden unbehandelter Fehler

Sie können Assertionen verwenden, um fehlerbedingungen an einem Punkt im Code zu testen, an dem fehler behandelt werden sollen. Im folgenden Beispiel gibt eine Grafikroutine einen Fehlercode oder null für den Erfolg zurück.

myErr = myGraphRoutine(a, b);

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

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

Wenn der Fehlerbehandlungscode ordnungsgemäß funktioniert, sollte der Fehler behandelt und myErr auf Null zurückgesetzt werden, bevor die Assertion erreicht wird. Wenn myErr ein anderer Wert vorhanden ist, schlägt die Assertion fehl, wird das Programm angehalten, und das Dialogfeld "Assertionsfehler" wird angezeigt.

Assertionsanweisungen sind jedoch kein Ersatz für Fehlerbehandlungscode. Das folgende Beispiel zeigt eine Assertionsanweisung, die zu Problemen im endgültigen Releasecode führen kann:

myErr = myGraphRoutine(a, b);

/* No Code to handle errors */

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

Dieser Code basiert auf der Assertionsanweisung, um die Fehlerbedingung zu behandeln. Daher wird jeder Fehlercode, der von myGraphRoutine zurückgegeben wird, im endgültigen Releasecode nicht behandelt bleiben.

In diesem Thema