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
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.
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.
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));
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 CObject
oggetto 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 AssertValid
di , 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 AssertValid
di ).
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 AssertValid
di . 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 AssertValid
di . 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 AssertValid
di .
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.
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.
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 mols
cui 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.
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.