Question

Je vois souvent du code hérité recherchant NULL avant de supprimer un pointeur, similaire à,

if (NULL != pSomeObject) 
{
    delete pSomeObject;
    pSomeObject = NULL;
}

Y a-t-il une raison de rechercher un pointeur NULL avant de le supprimer? Quelle est la raison pour définir le pointeur sur NULL par la suite?

Était-ce utile?

La solution

Il est parfaitement "sûr". supprimer un pointeur nul; cela équivaut effectivement à un non-op.

La raison pour laquelle vous voudrez peut-être vérifier la valeur NULL avant de supprimer est que tenter de supprimer un pointeur NULL pourrait indiquer un bogue dans votre programme.

Autres conseils

La norme C ++ garantit qu’il est légal d’utiliser un pointeur null dans une expression-suppression (& # 167; 8.5.2.5/2). Cependant, il est non spécifié si cela va appeler une fonction de désallocation ( opérateur delete ou opérateur delete [] ; & # 167; 8.5.2.5/ 7, note).

Si une fonction de désallocation par défaut (c'est-à-dire fournie par la bibliothèque standard) est appelée avec un pointeur nul, l'appel n'a aucun effet (& # 167; 6.6.4.4.2 / 3).

Mais il n’est pas précisé ce qui se passera si la fonction de désallocation n’est pas fournie par la bibliothèque standard & # 8212; & nbsp; i.e. que se passe-t-il lorsque nous surchargeons opérateur delete (ou opérateur delete [] ).

Un programmeur compétent gérera les pointeurs nuls en conséquence dans la fonction de désallocation plutôt qu'avant l'appel, comme indiqué dans le code de l'OP. De même, définissez le pointeur sur nullptr / NULL après la suppression n'a qu'un but très limité. Certaines personnes aiment le faire dans l’esprit de la programmation défensive : le comportement du programme en sera légèrement plus prévisible dans le cas d'un bogue: l'accès au pointeur après la suppression entraînera un accès nul au pointeur plutôt qu'un accès à un emplacement de mémoire aléatoire. Bien que les deux opérations aient un comportement indéfini, le comportement d'un accès de pointeur nul est beaucoup plus prévisible dans la pratique (il aboutit le plus souvent à un crash direct plutôt qu'à une corruption de la mémoire). Les corruptions de mémoire étant particulièrement difficiles à déboguer, la réinitialisation des pointeurs supprimés facilite le débogage.

& # 8212; Bien sûr, il s’agit de traiter le symptôme plutôt que la cause (c’est-à-dire le bogue). Vous devez considérer la réinitialisation des pointeurs comme une odeur de code. Un code C ++ propre et moderne clarifiera la propriété de la mémoire et le vérifiera de manière statique (à l'aide de pointeurs intelligents ou de mécanismes équivalents).

Bonus: explication de la suppression de l'opérateur surchargé :

opérateur delete est (malgré son nom) une fonction qui peut être surchargée comme toute autre fonction. Cette fonction est appelée en interne pour chaque appel de opérateur delete avec les arguments correspondants. Il en va de même pour opérateur new .

La surcharge de opérateur new (puis de opérateur delete ) est logique dans certaines situations lorsque vous souhaitez contrôler précisément la manière dont la mémoire est allouée. Faire cela n’est même pas très difficile, mais quelques précautions doivent être prises pour assurer un comportement correct. Scott Meyers décrit cela en détail Effective C ++ .

Pour l'instant, disons simplement que nous voulons surcharger la version globale de opérateur new pour le débogage. Avant de faire cela, un bref avis sur ce qui se passe dans le code suivant:

klass* pobj = new klass;
// … use pobj.
delete pobj;

Qu'est-ce qui se passe réellement ici? Eh bien, ce qui précède peut être traduit approximativement dans le code suivant:

// 1st step: allocate memory
klass* pobj = static_cast<klass*>(operator new(sizeof(klass)));
// 2nd step: construct object in that memory, using placement new:
new (pobj) klass();

// … use pobj.

// 3rd step: call destructor on pobj:
pobj->~klass();
// 4th step: free memory
operator delete(pobj);

Remarquez l'étape 2 où nous appelons new avec une syntaxe légèrement étrange. Ceci est un appel à ce qu'on appelle placement nouveau qui prend une adresse et construit un objet à cette adresse. Cet opérateur peut également être surchargé. Dans ce cas, il suffit d'appeler le constructeur de la classe klass .

Maintenant, sans plus attendre, voici le code d'une version surchargée des opérateurs:

void* operator new(size_t size) {
    // See Effective C++, Item 8 for an explanation.
    if (size == 0)
        size = 1;

    cerr << "Allocating " << size << " bytes of memory:";

    while (true) {
        void* ret = custom_malloc(size);

        if (ret != 0) {
            cerr << " @ " << ret << endl;
            return ret;
        }

        // Retrieve and call new handler, if available.
        new_handler handler = set_new_handler(0);
        set_new_handler(handler);

        if (handler == 0)
            throw bad_alloc();
        else
            (*handler)();
    }
}

void operator delete(void* p) {
    cerr << "Freeing pointer @ " << p << "." << endl;
    custom_free(p);
}

Ce code utilise simplement une implémentation personnalisée de malloc / free en interne, comme le font la plupart des implémentations. Il crée également une sortie de débogage. Considérez le code suivant:

int main() {
    int* pi = new int(42);
    cout << *pi << endl;
    delete pi;
}

Cela a généré le résultat suivant:

Allocating 4 bytes of memory: @ 0x100160
42
Freeing pointer @ 0x100160.

Désormais, ce code est fondamentalement différent de l'implémentation standard de opérateur delete : Il n'a pas testé les pointeurs nuls! Le compilateur ne le vérifie pas. le code ci-dessus est compilé, mais il peut générer des erreurs désagréables au moment de l'exécution lorsque vous essayez de supprimer les pointeurs nuls.

Cependant, comme je l’ai déjà dit, ce comportement est en fait inattendu et un auteur de bibliothèque devrait vérifier les pointeurs nuls dans l'opérateur delete . Cette version est bien améliorée:

void operator delete(void* p) {
    if (p == 0) return;
    cerr << "Freeing pointer @ " << p << "." << endl;
    free(p);
}

En conclusion, bien qu'une implémentation bâclée de opérateur delete puisse nécessiter des contrôles nuls explicites dans le code client, il s'agit d'un comportement non standard qui ne devrait être toléré que dans le support hérité ( si tous ).

Supprimer vérifie la valeur NULL en interne. Votre test est redondant

La suppression de null est un no-op. Il n'y a aucune raison de vérifier la valeur null avant d'appeler delete.

Vous pouvez également rechercher la valeur null pour d'autres raisons si le pointeur étant null contient des informations supplémentaires qui vous intéressent.

Selon C ++ 03 5.3.5 / 2, vous pouvez supprimer un pointeur null en toute sécurité. Ce qui suit est tiré de la norme:

  

Dans les deux cas, si la valeur de l'opérande de suppression est le   pointeur null l'opération n'a pas d'effet.

Si pSomeObject est NULL, la suppression ne fera rien. Donc, non, vous n'avez pas à vérifier NULL.

Nous considérons qu'il est judicieux d'attribuer la valeur NULL au pointeur après l'avoir supprimé, s'il est tout à fait possible qu'un knucklehead puisse essayer d'utiliser le pointeur. Utiliser un pointeur NULL est légèrement préférable à un pointeur indiquant qui sait quoi (le pointeur NULL provoquera un blocage, le pointeur sur la mémoire supprimée peut ne pas l'être)

Il n'y a aucune raison de rechercher la valeur NULL avant de la supprimer. Il peut être nécessaire d'attribuer la valeur NULL après la suppression si quelque part dans le code, on vérifie si un objet est déjà alloué en effectuant une vérification NULL. Un exemple serait une sorte de données en cache allouées à la demande. Chaque fois que vous effacez l'objet cache, vous affectez la valeur NULL au pointeur afin que le code qui alloue l'objet sache qu'il doit effectuer une allocation.

Je pense que le développeur précédent l'a codé "de manière redondante". pour économiser quelques millisecondes: Si vous supprimez le pointeur sur NULL est une bonne chose. Vous pouvez donc utiliser une ligne comme celle-ci juste après la suppression de l'objet:

if(pSomeObject1!=NULL) pSomeObject1=NULL;

Mais alors delete fait quand même cette comparaison exacte (ne rien faire si c'est NULL). Pourquoi faire ça deux fois? Vous pouvez toujours affecter pSomeObject à NULL après l'appel de delete, quelle que soit sa valeur actuelle - mais cela serait légèrement redondant s'il avait déjà cette valeur.

Mon pari est donc que l'auteur de ces lignes a essayé de s'assurer que pSomeObject1 serait toujours NULL après avoir été supprimé, sans entraîner le coût d'un test et d'une assignation potentiellement inutiles.

Cela dépend de ce que vous faites. Certaines implémentations plus anciennes de free , par exemple, ne seront pas satisfait si un pointeur NULL leur est transmis. Certaines bibliothèques ont encore ce problème. Par exemple, XFree dans la bibliothèque Xlib dit:

  

DESCRIPTION

     

La fonction XFree est un   routine Xlib à usage général qui   libère les données spécifiées. Vous devez   l'utiliser pour libérer tous les objets qui étaient   attribué par Xlib, sauf si un remplaçant   la fonction est explicitement spécifiée pour   L'object. Un pointeur NULL ne peut pas être   passé à cette fonction.

Envisagez donc de libérer les pointeurs NULL en tant que bogue et vous serez en sécurité.

En ce qui concerne mes observations, la suppression d’un pointeur NULL à l’aide de la suppression est sûre sur les machines Unix telles que PARISC et Itanium. Mais cela est assez dangereux pour les systèmes Linux car le processus se bloquerait alors.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top