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

Fonctionnement des assertions

Assertions dans les builds Debug et Release

Effets secondaires de l’utilisation d’assertions

Assertions CRT

Assertions MFC

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.

Dans cette rubrique

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.

Dans cette rubrique

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));

Dans cette rubrique

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.

Dans cette rubrique

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.

Dans cette rubrique

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.

Dans cette rubrique

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.

Dans cette rubrique