web-dev-qa-db-fra.com

Double libre ou corruption après la file d'attente :: push

#include <queue>
using namespace std;

class Test{
    int *myArray;

        public:
    Test(){
        myArray = new int[10];
    }

    ~Test(){
        delete[] myArray;
    }

};


int main(){
    queue<Test> q
    Test t;
    q.Push(t);
}

Après avoir exécuté ceci, j'obtiens une erreur d'exécution "double libre ou corruption". Si je me débarrasse du contenu du destructeur (le delete), cela fonctionne bien. Qu'est-ce qui ne va pas?

49
Mihai Neacsu

Parlons de la copie d'objets en C++.

Test t;, Appelle le constructeur par défaut, qui alloue un nouveau tableau d'entiers. C'est bien ainsi que votre comportement attendu.

Des problèmes surviennent lorsque vous insérez t dans votre file d'attente à l'aide de q.Push(t). Si vous connaissez Java, C # ou presque tout autre langage orienté objet, vous pouvez vous attendre à ce que l’objet que vous avez créé créé soit ajouté à la file d’attente, mais C++ ne fonctionne pas de cette façon.

Lorsque nous examinons méthode std::queue::Push , nous voyons que l'élément ajouté à la file d'attente est "initialisé sur une copie de x." Il s'agit en fait d'un nouvel objet qui utilise le constructeur de copie pour dupliquer chaque membre de votre objet Test d'origine afin de créer un nouveau Test.

Votre compilateur C++ génère un constructeur de copie pour vous par défaut! C'est très pratique, mais pose des problèmes avec les membres du pointeur. Dans votre exemple, rappelez-vous que int *myArray N'est qu'une adresse mémoire; Lorsque la valeur de myArray est copiée de l'ancien objet vers le nouvel objet, vous avez maintenant deux objets pointant vers le même tableau en mémoire. Ce n'est pas intrinsèquement mauvais, mais le destructeur essaiera ensuite de supprimer le même tableau deux fois, d'où l'erreur d'exécution "double free or corruption".

Comment je le répare?

La première étape consiste à implémenter un constructeur de copie , capable de copier en toute sécurité les données d'un objet à un autre. Pour simplifier, cela pourrait ressembler à ceci:

Test(const Test& other){
    myArray = new int[10];
    memcpy( myArray, other.myArray, 10 );
}

Désormais, lorsque vous copiez des objets de test, un nouveau tableau sera alloué pour le nouvel objet et les valeurs du tableau seront également copiées.

Nous ne sommes pas encore complètement sortis du pétrin. Le compilateur génère une autre méthode qui pourrait entraîner des problèmes similaires, à savoir l’affectation. La différence est qu'avec l'assignation, nous avons déjà un objet existant dont la mémoire doit être gérée correctement. Voici une implémentation d'opérateur d'affectation de base:

Test& operator= (const Test& other){
    if (this != &other) {
        memcpy( myArray, other.myArray, 10 );
    }
    return *this;
}

La partie importante ici est que nous copions les données de l'autre tableau dans le tableau de cet objet, en gardant la mémoire de chaque objet séparée. Nous avons également un chèque pour l'auto-affectation; sinon, nous copierions de nous-mêmes à nous-mêmes, ce qui pourrait générer une erreur (incertain de ce que cela est supposé faire) Si nous supprimions et allouions plus de mémoire, la vérification de l'auto-affectation nous empêche de supprimer la mémoire à copier.

84
derekerdmann

Le problème est que votre classe contient un pointeur RAW géré mais n'implémente pas la règle de trois (cinq en C++ 11). En conséquence, vous obtenez (normalement) une double suppression à cause de la copie.

Si vous apprenez, vous devriez apprendre à appliquer la règle de trois (cinq) . Mais ce n'est pas la bonne solution à ce problème. Vous devriez utiliser des objets de conteneur standard plutôt que d'essayer de gérer votre propre conteneur interne. Le conteneur exact dépendra de ce que vous essayez de faire, mais std :: vector est un bon choix par défaut (et vous pouvez changer les mots suivants si ce n’est pas opimal).

#include <queue>
#include <vector>

class Test{
    std::vector<int> myArray;

    public:
    Test(): myArray(10){
    }    
};

int main(){
    queue<Test> q
    Test t;
    q.Push(t);
}

La raison pour laquelle vous devez utiliser un conteneur standard est le separation of concerns. Votre classe devrait s'intéresser à la logique métier ou à la gestion des ressources (pas aux deux). En supposant que Test soit une classe que vous utilisez pour conserver un état de votre programme, il s’agit d’une logique métier qui ne devrait pas faire de la gestion des ressources. Si, par contre, Test est supposé gérer un tableau, vous devez probablement en savoir plus sur ce qui est disponible dans la bibliothèque standard.

15
Martin York

Vous obtenez double libre ou corruption car le premier destructeur est pour l'objet q dans ce cas, la mémoire allouée par nouvea sera libre. Le prochain moment où le destructeur sera appelé pour l'objet t à ce moment la mémoire est déjà libre (fait pour q) donc quand dans destructor delete [] myArray; va l'exécuter va lancer - double libre ou corruption. La raison en est que les deux objets partageant la même mémoire définissent donc\copy, affectation, et opérateur égal comme mentionné dans la réponse ci-dessus.

4
Astro - Amit

Vous devez définir un constructeur de copie, une affectation, un opérateur.

class Test {
   Test(const Test &that); //Copy constructor
   Test& operator= (const Test &rhs); //assignment operator
}

Votre copie insérée dans la file d'attente pointe vers la même mémoire que votre original. Lorsque le premier est détruit, il efface la mémoire. Le second se détruit et tente de supprimer la même mémoire.

3
Ryan Guthrie

Vous pouvez également essayer de vérifier NULL avant de supprimer de telle sorte que

if(myArray) { delete[] myArray; myArray = NULL; }

ou vous pouvez définir toutes les opérations de suppression de manière sécurisée comme ceci:

#ifndef SAFE_DELETE
#define SAFE_DELETE(p) { if(p) { delete (p); (p) = NULL; } }
#endif

#ifndef SAFE_DELETE_ARRAY
#define SAFE_DELETE_ARRAY(p) { if(p) { delete[] (p); (p) = NULL; } }
#endif

et ensuite utiliser

SAFE_DELETE_ARRAY(myArray);
0
tolgayilmaz