Assertions C/C++
Une instruction d’assertion spécifie une condition supposée être vraie en un point de votre programme. Si cette condition n'est pas vraie, l'assertion échoue, l'exécution de votre programme est interrompue et la boîte de dialogue Échec de l’assertion apparaît.
Visual Studio prend en charge les instructions d’assertion C++ basées sur les constructions suivantes :
Assertions MFC pour les programmes MFC.
ATLASSERT pour les programmes qui utilisent ATL.
Assertions CRT pour les programmes qui utilisent la bibliothèque runtime C.
Fonction d’assertion ANSI pour d’autres programmes C/C++.
Vous pouvez utiliser des assertions pour intercepter les erreurs logiques, vérifier les résultats d’une opération et tester les conditions d’erreur qui auraient dû être gérées.
Dans cette rubrique
Assertions dans les builds Debug et Release
Effets secondaires de l’utilisation d’assertions
Fonctionnement des assertions
Lorsque le débogueur s’arrête en raison d’une assertion de bibliothèque runtime MFC ou C, si la source est disponible, le débogueur accède au point dans le fichier source où l’assertion s’est produite. Le message d’assertion s’affiche à la fois dans la fenêtre Sortie et dans la boîte de dialogue Échec de l’assertion. Vous pouvez copier le message d’assertion de la fenêtre Sortie dans une fenêtre de texte si vous souhaitez l’enregistrer pour référence ultérieure. La fenêtre Sortie peut également contenir d’autres messages d’erreur. Examinez attentivement ces messages, car ils fournissent des indices sur la cause de l’échec d’assertion.
Utilisez des assertions pour détecter les erreurs pendant le développement. En règle générale, utilisez une assertion pour chaque hypothèse. Par exemple, si vous partez du principe qu’un argument n’est pas NULL, utilisez une assertion pour tester cette hypothèse.
Assertions dans les builds Debug et Release
Les instructions d’assertion se compilent uniquement si _DEBUG
est défini. Sinon, le compilateur traite les assertions comme des instructions nulles. Par conséquent, les instructions d’assertion n’imposent aucune surcharge ou coût de performances dans votre programme de version finale et vous permettent d’éviter d’utiliser des directives #ifdef
.
Effets secondaires de l’utilisation d’assertions
Lorsque vous ajoutez des assertions à votre code, assurez-vous que les assertions n’ont pas d’effets secondaires. Par exemple, considérez l’assertion suivante qui modifie la valeur nM
:
ASSERT(nM++ > 0); // Don't do this!
Étant donné que l’expression ASSERT
n’est pas évaluée dans la version Release de votre programme, nM
aura des valeurs différentes dans les versions Debug et Release. Pour éviter ce problème dans MFC, vous pouvez utiliser la macro VERIFY au lieu de ASSERT
. VERIFY
évalue l’expression dans toutes les versions, mais ne vérifie pas le résultat dans la version Release.
Soyez particulièrement prudent lors de l’utilisation d’appels de fonction dans les instructions d’assertion, car l’évaluation d’une fonction peut avoir des effets secondaires inattendus.
ASSERT ( myFnctn(0)==1 ) // unsafe if myFnctn has side effects
VERIFY ( myFnctn(0)==1 ) // safe
VERIFY
appelle myFnctn
à la fois dans les versions Debug et Release, il est donc acceptable d’utiliser. Toutefois, l’utilisation VERIFY
impose la surcharge d’un appel de fonction inutile dans la version Release.
Assertions CRT
Le fichier d’en-tête CRTDBG.H
définit les macros _ASSERT
et _ASSERTE
pour la vérification des assertions.
Macro | Résultats |
---|---|
_ASSERT |
Si l’expression spécifiée prend la valeur FALSE, le nom de fichier et le numéro de ligne du _ASSERT . |
_ASSERTE |
Identique à _ASSERT , plus une représentation sous forme de chaîne de l’expression qui a été affirmée. |
_ASSERTE
est plus puissant, car il signale l’expression affirmée qui s’est avérée être FALSE. Cela pourrait être suffisant pour identifier le problème sans faire référence au code source. Toutefois, la version de débogage de votre application contient une constante de chaîne pour chaque expression déclarée à l’aide de _ASSERTE
. Si vous utilisez de nombreuses macros _ASSERTE
, ces expressions de chaîne occupent une quantité importante de mémoire. Si cela s’avère être un problème, utilisez _ASSERT
pour économiser de la mémoire.
Lorsque _DEBUG
est défini, la macro _ASSERTE
est définie comme suit :
#define _ASSERTE(expr) \
do { \
if (!(expr) && (1 == _CrtDbgReport( \
_CRT_ASSERT, __FILE__, __LINE__, #expr))) \
_CrtDbgBreak(); \
} while (0)
Si l’expression déclarée prend la valeur FALSE, _CrtDbgReport est appelé pour signaler l’échec de l’assertion (à l’aide d’une boîte de dialogue de message par défaut). Si vous choisissez Réessayer dans la boîte de dialogue du message, _CrtDbgReport
retourne 1 et _CrtDbgBreak
appelle le débogueur via DebugBreak
.
Si vous devez désactiver temporairement toutes les assertions, utilisez _CtrSetReportMode.
Vérification de l'altération du tas
L’exemple suivant utilise _CrtCheckMemory pour vérifier l’altération du tas :
_ASSERTE(_CrtCheckMemory());
Vérification de la validité du pointeur
L’exemple suivant utilise _CrtIsValidPointer pour vérifier qu’une plage de mémoire donnée est valide pour la lecture ou l’écriture.
_ASSERTE(_CrtIsValidPointer( address, size, TRUE );
L’exemple suivant utilise _CrtIsValidHeapPointer pour vérifier qu’un pointeur pointe vers la mémoire dans le tas local (le tas créé et géré par cette instance de la bibliothèque runtime C ; une DLL peut avoir sa propre instance de la bibliothèque, et donc son propre tas, en dehors du tas d’application). Cette assertion intercepte non seulement les adresses nulles ou hors limites, mais également les pointeurs vers des variables statiques, des variables de pile et toute autre mémoire non locale.
_ASSERTE(_CrtIsValidHeapPointer( myData );
Vérification d’un bloc de mémoire
L’exemple suivant utilise _CrtIsMemoryBlock pour vérifier qu’un bloc de mémoire se trouve dans le tas local et a un type de bloc valide.
_ASSERTE(_CrtIsMemoryBlock (myData, size, &requestNumber, &filename, &linenumber));
Assertions MFC
MFC définit la macro ASSERT pour la vérification des assertions. Il définit également les méthodes MFC ASSERT_VALID
et CObject::AssertValid
pour vérifier l’état interne d’un objet dérivé CObject
.
Si l’argument de la macro ASSERT
MFC prend la valeur zéro ou false, la macro arrête l’exécution du programme et alerte l’utilisateur ; sinon, l’exécution continue.
Lorsqu’une assertion échoue, une boîte de dialogue de message affiche le nom du fichier source et le numéro de ligne de l’assertion. Si vous choisissez Réessayer dans la boîte de dialogue, un appel à AfxDebugBreak entraîne l’interruption de l’exécution du débogueur. À ce stade, vous pouvez examiner la pile des appels et utiliser d’autres fonctionnalités de débogueur pour déterminer pourquoi l’assertion a échoué. Si vous avez activé le débogage juste-à-temps et que le débogueur n’était pas déjà en cours d’exécution, la boîte de dialogue peut lancer le débogueur.
L’exemple suivant montre comment utiliser ASSERT
pour vérifier la valeur renvoyée d’une fonction :
int x = SomeFunc(y);
ASSERT(x >= 0); // Assertion fails if x is negative
Vous pouvez utiliser ASSERT avec la fonction IsKindOf pour fournir le contrôle de type des arguments de fonction :
ASSERT( pObject1->IsKindOf( RUNTIME_CLASS( CPerson ) ) );
La macro ASSERT
ne produit aucun code dans la version Release. Si vous devez évaluer l’expression dans la version Release, utilisez la macro VERIFY au lieu d’ASSERT.
MFC ASSERT_VALID et CObject::AssertValid
La méthode CObject::AssertValid fournit des vérifications au moment de l’exécution de l’état interne d’un objet. Bien que vous ne soyez pas obligé de remplacer AssertValid
lorsque vous dérivez votre classe de CObject
, vous pouvez rendre votre classe plus fiable en procédant ainsi. AssertValid
doit effectuer des assertions sur toutes les variables membres de l’objet pour vérifier qu’elles contiennent des valeurs valides. Par exemple, il doit vérifier que les variables membres du pointeur ne sont pas NULL.
L'exemple suivant montre comment déclarer une fonction AssertValid
:
class CPerson : public CObject
{
protected:
CString m_strName;
float m_salary;
public:
#ifdef _DEBUG
// Override
virtual void AssertValid() const;
#endif
// ...
};
Lorsque vous remplacez AssertValid
, appelez la version de la classe de base de AssertValid
avant d’effectuer vos propres vérifications. Ensuite, utilisez la macro ASSERT pour vérifier les membres propres à votre classe dérivée, comme illustré ici :
#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
Si l’une de vos variables membres stocke des objets, vous pouvez utiliser la macro ASSERT_VALID
pour tester sa validité interne (si ses classes remplacent AssertValid
).
Par exemple, considérez une classe CMyData
, qui stocke un CObList dans l’une de ses variables membres. La variableCObList
, m_DataList
, stocke une collection d’objets CPerson
. Une déclaration abrégée de CMyData
ressemble à ceci :
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 ...
};
Le remplacement de AssertValid
dans CMyData
se présente comme suit :
#ifdef _DEBUG
void CMyData::AssertValid( ) const
{
// Call inherited AssertValid.
CObject::AssertValid( );
// Check validity of CMyData members.
ASSERT_VALID( m_pDataList );
// ...
}
#endif
CMyData
utilise le mécanisme AssertValid
pour tester la validité des objets stockés dans son membre de données. Le remplacement AssertValid
de CMyData
appelle la macro ASSERT_VALID
pour sa propre variable membre m_pDataList.
Le test de validité ne s’arrête pas à ce niveau, car la classe CObList
remplace également AssertValid
. Ce remplacement effectue des tests de validité supplémentaires sur l’état interne de la liste. Ainsi, un test de validité sur un objet CMyData
conduit à des tests de validité supplémentaires pour les états internes de l’objet CObList
stockée dans la liste.
Avec plus de travail, vous pouvez également ajouter des tests de validité pour les objets CPerson
stockés dans la liste. Vous pouvez dériver une classe CPersonList
de CObList
et remplacer AssertValid
. Dans le remplacement, vous appelez CObject::AssertValid
, puis effectuez une itération dans la liste, en appelant AssertValid
sur chaque objet CPerson
stocké dans la liste. La classe CPerson
indiquée au début de cette rubrique remplace déjà AssertValid
.
Il s’agit d’un mécanisme puissant lorsque vous générez pour le débogage. Lorsque vous générez par la suite pour la mise en production, le mécanisme est automatiquement désactivé.
Limitations d’AssertValid
Une assertion déclenchée indique que l’objet est définitivement incorrect et que l’exécution s’arrête. Toutefois, une absence d’assertion indique uniquement qu’aucun problème n’a été trouvé, mais que l’objet n’est pas garanti pour être bon.
Utilisation d’assertions
Intercepter les erreurs logiques
Vous pouvez définir une assertion sur une condition qui doit être true en fonction de la logique de votre programme. L’assertion n’a aucun effet, sauf si une erreur logique se produit.
Par exemple, supposons que vous simulez des molécules de gaz dans un conteneur et que la variable numMols
représente le nombre total de molécules. Ce nombre ne pouvant pas être inférieur à zéro, vous pouvez donc inclure une instruction d’assertion MFC comme suit :
ASSERT(numMols >= 0);
Vous pouvez également inclure une assertion CRT comme suit :
_ASSERT(numMols >= 0);
Ces instructions ne font rien si votre programme fonctionne correctement. Si une erreur logique fait que numMols
est inférieure à zéro, toutefois, l’assertion arrête l’exécution de votre programme et affiche la boîte de dialogue Échec de l’assertion.
Vérification des résultats
Les assertions sont précieuses pour les opérations de test dont les résultats ne sont pas évidents à partir d’une inspection visuelle rapide.
Par exemple, considérez le code suivant, qui met à jour la variable iMols
en fonction du contenu de la liste liée pointée par 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
Le nombre de molécules comptées par iMols
doit toujours être inférieur ou égal au nombre total de molécules, numMols
. L’inspection visuelle de la boucle n’indique pas que ce sera nécessairement le cas, donc une instruction d’assertion est utilisée après la boucle pour tester cette condition.
Recherche d’erreurs non gérées
Vous pouvez utiliser des assertions pour tester les conditions d’erreur à un point de votre code où toutes les erreurs auraient dû être gérées. Dans l’exemple suivant, une routine graphique retourne un code d’erreur ou zéro pour la réussite.
myErr = myGraphRoutine(a, b);
/* Code to handle errors and
reset myErr if successful */
ASSERT(!myErr); -- MFC version
_ASSERT(!myErr); -- CRT version
Si le code de gestion des erreurs fonctionne correctement, l’erreur doit être gérée et myErr
réinitialisée à zéro avant que l’assertion soit atteinte. Si myErr
a une autre valeur, l’assertion échoue, le programme s’arrête et la boîte de dialogue Échec de l’assertion s’affiche.
Toutefois, les instructions d’assertion ne remplacent pas le code de gestion des erreurs. L’exemple suivant montre une instruction d’assertion qui peut entraîner des problèmes dans le code de version finale :
myErr = myGraphRoutine(a, b);
/* No Code to handle errors */
ASSERT(!myErr); // Don't do this!
_ASSERT(!myErr); // Don't do this, either!
Ce code s’appuie sur l’instruction d’assertion pour gérer la condition d’erreur. Par conséquent, tout code d’erreur retourné par myGraphRoutine
ne sera pas pris en charge dans le code de version finale.