Condividi tramite


Asserzioni C/C++

Un'istruzione di asserzione specifica una condizione che si prevede di essere true in un punto del programma. Se tale condizione non è true, l'asserzione ha esito negativo, l'esecuzione del programma viene interrotta e viene visualizzata la finestra di dialogo Asserzione non riuscita.

Visual Studio supporta istruzioni di asserzione C++ basate sui costrutti seguenti:

  • Asserzioni MFC per i programmi MFC.

  • ATLAS edizione Standard RT per i programmi che usano ATL.

  • Asserzioni CRT per programmi che usano la libreria di runtime C.

  • Funzione asserzione ANSI per altri programmi C/C++.

    È possibile usare le asserzioni per rilevare gli errori logici, controllare i risultati di un'operazione e testare le condizioni di errore che devono essere state gestite.

Contenuto dell'argomento

Funzionamento delle asserzioni

Asserzioni nelle compilazioni di debug e rilascio

Effetti collaterali dell'uso delle asserzioni

Asserzioni CRT

Asserzioni MFC

Funzionamento delle asserzioni

Quando il debugger si interrompe a causa di un'asserzione della libreria di runtime MFC o C, se l'origine è disponibile, il debugger passa al punto nel file di origine in cui si è verificata l'asserzione. Il messaggio di asserzione viene visualizzato sia nella finestra output che nella finestra di dialogo Asserzione non riuscita. È possibile copiare il messaggio di asserzione dalla finestra Output in una finestra di testo se si desidera salvarlo per riferimento futuro. La finestra Output potrebbe contenere anche altri messaggi di errore. Esaminare attentamente questi messaggi, perché forniscono indizi sulla causa dell'errore di asserzione.

Usare le asserzioni per rilevare gli errori durante lo sviluppo. Come regola, usare un'asserzione per ogni presupposto. Ad esempio, se si presuppone che un argomento non sia NULL, usare un'asserzione per verificare tale presupposto.

Contenuto dell'argomento

Asserzioni nelle compilazioni di debug e rilascio

Le istruzioni di asserzione vengono compilate solo se _DEBUG è definito. In caso contrario, il compilatore considera le asserzioni come istruzioni Null. Pertanto, le istruzioni di asserzione non impongono costi generali o di prestazioni nel programma di rilascio finale e consentono di evitare l'uso #ifdef di direttive.

Effetti collaterali dell'uso delle asserzioni

Quando si aggiungono asserzioni al codice, assicurarsi che le asserzioni non abbiano effetti collaterali. Si consideri ad esempio l'asserzione seguente che modifica il nM valore:

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

Poiché l'espressione ASSERT non viene valutata nella versione release del programma, nM avrà valori diversi nelle versioni di debug e rilascio. Per evitare questo problema in MFC, è possibile utilizzare la macro VERIFY anziché ASSERT. VERIFY valuta l'espressione in tutte le versioni, ma non controlla il risultato nella versione release.

Prestare particolare attenzione all'uso delle chiamate di funzione nelle istruzioni di asserzione, perché la valutazione di una funzione può avere effetti collaterali imprevisti.

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

VERIFY chiama myFnctn entrambe le versioni di Debug e Release, quindi è accettabile usare. Tuttavia, l'uso VERIFY di impone il sovraccarico di una chiamata di funzione non necessaria nella versione release.

Contenuto dell'argomento

Asserzioni CRT

Il CRTDBG.H file di intestazione definisce le macro e _ASSERTE per il _ASSERT controllo delle asserzioni.

Macro Risultato
_ASSERT Se l'espressione specificata restituisce FAL edizione Standard, il nome del file e il numero di riga dell'oggetto _ASSERT.
_ASSERTE Uguale a _ASSERT, più una rappresentazione di stringa dell'espressione asserzione.

_ASSERTEè più potente perché segnala l'espressione asserta che si è rivelata FAL edizione Standard. Questo potrebbe essere sufficiente per identificare il problema senza fare riferimento al codice sorgente. Tuttavia, la versione Debug dell'applicazione conterrà una costante stringa per ogni espressione asserta usando _ASSERTE. Se si usano molte _ASSERTE macro, queste espressioni stringa occupano una quantità significativa di memoria. Se si verifica un problema, usare _ASSERT per salvare la memoria.

Quando _DEBUG viene definita, la _ASSERTE macro viene definita come segue:

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

Se l'espressione asserta restituisce FAL edizione Standard, _CrtDbgReport viene chiamato per segnalare l'errore di asserzione (usando una finestra di dialogo di messaggio per impostazione predefinita). Se si sceglie Riprova nella finestra di dialogo del messaggio, _CrtDbgReport restituisce 1 e _CrtDbgBreak chiama il debugger tramite DebugBreak.

Se è necessario disabilitare temporaneamente tutte le asserzioni, usare _CtrSetReportMode.

Controllo del danneggiamento dell'heap

L'esempio seguente usa _CrtCheckMemory per verificare la presenza di danneggiamento dell'heap:

_ASSERTE(_CrtCheckMemory());

Verifica della validità del puntatore

L'esempio seguente usa _CrtIsValidPointer per verificare che un intervallo di memoria specificato sia valido per la lettura o la scrittura.

_ASSERTE(_CrtIsValidPointer( address, size, TRUE );

L'esempio seguente usa _CrtIsValidHeapPointer per verificare che un puntatore punti alla memoria nell'heap locale (l'heap creato e gestito da questa istanza della libreria di runtime C, una DLL può avere una propria istanza della libreria e quindi il proprio heap, all'esterno dell'heap dell'applicazione). Questa asserzione rileva non solo indirizzi Null o out-of-bounds, ma anche puntatori a variabili statiche, variabili dello stack e qualsiasi altra memoria non locale.

_ASSERTE(_CrtIsValidHeapPointer( myData );

Controllo di un blocco di memoria

L'esempio seguente usa _CrtIsMemoryBlock per verificare che un blocco di memoria si trova nell'heap locale e abbia un tipo di blocco valido.

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

Contenuto dell'argomento

Asserzioni MFC

MFC definisce la macro AS edizione Standard RT per il controllo delle asserzioni. Definisce anche i MFC ASSERT_VALID metodi e CObject::AssertValid per controllare lo stato interno di un CObjectoggetto derivato da .

Se l'argomento della macro MFC ASSERT restituisce zero o false, la macro interrompe l'esecuzione del programma e avvisa l'utente; in caso contrario, l'esecuzione continua.

Quando un'asserzione ha esito negativo, una finestra di dialogo di messaggio mostra il nome del file di origine e il numero di riga dell'asserzione. Se si sceglie Riprova nella finestra di dialogo, una chiamata a AfxDebugBreak causa l'interruzione dell'esecuzione nel debugger. A questo punto, è possibile esaminare lo stack di chiamate e usare altre strutture del debugger per determinare il motivo per cui l'asserzione non è riuscita. Se è stato abilitato il debug JIT e il debugger non era già in esecuzione, la finestra di dialogo può avviare il debugger.

L'esempio seguente illustra come usare ASSERT per controllare il valore restituito di una funzione:

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

È possibile usare AS edizione Standard RT con la funzione IsKindOf per fornire il controllo del tipo degli argomenti della funzione:

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

La ASSERT macro non produce codice nella versione release. Se è necessario valutare l'espressione nella versione release, usare la macro VERIFY anziché AS edizione Standard RT.

MFC AS edizione Standard RT_VALID e CObject::AssertValid

Il metodo CObject::AssertValid fornisce controlli in fase di esecuzione dello stato interno di un oggetto. Anche se non è necessario eseguire l'override AssertValid quando si deriva la classe da CObject, è possibile rendere la classe più affidabile eseguendo questa operazione. AssertValid deve eseguire asserzioni su tutte le variabili membro dell'oggetto per verificare che contengano valori validi. Ad esempio, deve verificare che le variabili membro del puntatore non siano NULL.

L'esempio seguente illustra come dichiarare una AssertValid funzione:

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

Quando si esegue l'override AssertValiddi , chiamare la versione della classe base di AssertValid prima di eseguire controlli personalizzati. Usare quindi la macro AS edizione Standard RT per controllare i membri univoci per la classe derivata, come illustrato di seguito:

#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

Se una delle variabili membro archivia oggetti , è possibile usare la macro per testarne la ASSERT_VALID validità interna (se le relative classi eseguono l'override AssertValiddi ).

Si consideri, ad esempio, una classe CMyData, che archivia un oggetto CObList in una delle variabili membro. La CObList variabile , m_DataList, archivia una raccolta di CPerson oggetti . Una dichiarazione abbreviata di CMyData è simile alla seguente:

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

L'override AssertValid in CMyData è simile al seguente:

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

CMyData utilizza il AssertValid meccanismo per verificare la validità degli oggetti archiviati nel relativo membro dati. L'override AssertValid di CMyData richiama la ASSERT_VALID macro per la propria variabile membro m_pDataList.

Il test di validità non si arresta a questo livello perché la classe CObList esegue anche l'override AssertValiddi . Questa sostituzione esegue test di validità aggiuntivi sullo stato interno dell'elenco. Pertanto, un test di validità su un CMyData oggetto comporta test di validità aggiuntivi per gli stati interni dell'oggetto elenco archiviato CObList .

Con altre operazioni, è anche possibile aggiungere test di validità per gli CPerson oggetti archiviati nell'elenco. È possibile derivare una classe CPersonList da CObList ed eseguire l'override AssertValiddi . Nell'override chiamare CObject::AssertValid e quindi scorrere l'elenco chiamando AssertValid su ogni CPerson oggetto archiviato nell'elenco. La CPerson classe illustrata all'inizio di questo argomento esegue già l'override AssertValiddi .

Si tratta di un meccanismo potente quando si compila per il debug. Quando successivamente si compila per il rilascio, il meccanismo viene disattivato automaticamente.

Limitazioni di AssertValid

Un'asserzione attivata indica che l'oggetto è sicuramente non valido e l'esecuzione verrà arrestata. Tuttavia, una mancanza di asserzione indica solo che non è stato trovato alcun problema, ma l'oggetto non è garantito che sia valido.

Contenuto dell'argomento

Uso delle asserzioni

Rilevamento degli errori logici

È possibile impostare un'asserzione in una condizione che deve essere vera in base alla logica del programma. L'asserzione non ha alcun effetto a meno che non si verifichi un errore di logica.

Si supponga, ad esempio, di simulare molecole di gas in un contenitore e che la variabile numMols rappresenti il numero totale di molecole. Questo numero non può essere minore di zero, pertanto è possibile includere un'istruzione di asserzione MFC simile alla seguente:

ASSERT(numMols >= 0);

In alternativa, è possibile includere un'asserzione CRT simile alla seguente:

_ASSERT(numMols >= 0);

Queste istruzioni non eseguono alcuna operazione se il programma funziona correttamente. Se un errore logico causa numMols un errore minore di zero, tuttavia, l'asserzione interrompe l'esecuzione del programma e visualizza la finestra di dialogo Asserzione non riuscita.

Contenuto dell'argomento

Controllo dei risultati

Le asserzioni sono utili per le operazioni di test i cui risultati non sono evidenti da un rapido controllo visivo.

Si consideri ad esempio il codice seguente, che aggiorna la variabile iMols in base al contenuto dell'elenco collegato a molscui punta :

/* 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

Il numero di molecole conteggiate da iMols deve essere sempre minore o uguale al numero totale di molecole, numMols. L'ispezione visiva del ciclo non mostra che questo sarà necessariamente il caso, quindi un'istruzione di asserzione viene usata dopo il ciclo per verificare la condizione.

Contenuto dell'argomento

Individuazione di errori non gestiti

È possibile usare le asserzioni per verificare le condizioni di errore in un punto del codice in cui devono essere gestiti eventuali errori. Nell'esempio seguente una routine grafica restituisce un codice di errore o zero per l'esito positivo.

myErr = myGraphRoutine(a, b);

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

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

Se il codice di gestione degli errori funziona correttamente, l'errore deve essere gestito e myErr reimpostato su zero prima che venga raggiunta l'asserzione. Se myErr ha un altro valore, l'asserzione ha esito negativo, il programma si interrompe e viene visualizzata la finestra di dialogo Asserzione non riuscita.

Le istruzioni di asserzione non sono tuttavia un sostituto del codice di gestione degli errori. L'esempio seguente mostra un'istruzione di asserzione che può causare problemi nel codice di versione finale:

myErr = myGraphRoutine(a, b);

/* No Code to handle errors */

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

Questo codice si basa sull'istruzione di asserzione per gestire la condizione di errore. Di conseguenza, qualsiasi codice di errore restituito da myGraphRoutine verrà non gestito nel codice di versione finale.

Contenuto dell'argomento