18 votes

Fuite de mémoire avec std::string lors de l'utilisation de std::list<std::string>.

Je travaille avec std::list<std::string> dans mon projet actuel. Mais il y a une fuite de mémoire quelque part liée à cela. J'ai donc testé le code problématique séparément :

#include <iostream>
#include <string>
#include <list>

class Line {
public:
    Line();
    ~Line();
    std::string* mString;
};

Line::Line() {
    mString = new std::string("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
}

Line::~Line() {
    //mString->clear(); // should not be neccessary
    delete mString;
}

int main(int argc, char** argv)
{
    // no memory leak
    while (1==1) {
        std::string *test = new std::string("XXXXXXXXXXXXXXXXXXXXXXXX");
        delete test;
    }

    // LEAK!
    // This causes a memory overflow, because the string thats added
    // to the list is not deleted when the list is deleted.
    while (1==1) {
        std::list<std::string> *sl = new std::list<std::string>;
        std::string *s = new std::string("XXXXXXXXXXXXXXXXXXXXXXX");
        sl->push_back(*s);
        //sl->pop_back(); //doesn't delete the string?- just the pointer
        delete sl;
    }

    // LEAK!
    // Here the string IS deleted, but the memory does still fill up
    // but slower
    while (1==1) {
        std::list<Line> *sl = new std::list<Line>;
        Line *s = new Line();
        sl->push_back(*s);
        //sl->pop_back(); //does delete the Line-Element
        sl->clear();
        delete sl;
    }
    return 0;

    // this does not cause any noticable memory leak
    while (1==1) {
        std::list<int> *sl = new std::list<int>;
        int i = 0xFFFF;
        sl->push_back(i);
        sl->clear();
        delete sl;
    }
    return 0;

    // This does not cause any overflow or leak
    while (1==1) {
        int *i;
        i= new int [9999];
        delete[] i;
    }

}

Pourquoi ma liste de chaînes de caractères provoque-t-elle une fuite de mémoire ? La suppression de la liste ne devrait-elle pas entraîner l'appel des destructeurs pour chaque chaîne contenue ?

31voto

grddev Points 1740

Dans le premier cas, le list n'a aucune idée que vous avez alloué la chaîne avec new et ne peut pas le supprimer. En particulier, la liste ne contient jamais qu'une copie de la chaîne que vous avez passée.

De même, dans le second cas, vous ne libérez jamais l'objet ligne. s et donc vous perdez de la mémoire. La raison pour laquelle la chaîne interne est supprimée est que vous n'avez pas correctement implémenté un constructeur de copie. Ainsi, si vous faites une copie d'un Line les deux référenceront le même pointeur de chaîne, et si vous essayez de les supprimer tous les deux, vous aurez des problèmes.

14voto

Michael Burr Points 181287

Votre classe Line a besoin d'un vecteur de copie et d'un opérateur d'affectation qui traitent correctement le pointeur de chaîne.

Sinon, il suffit d'avoir un std::string plutôt qu'un pointeur et laisser le string gère la mémoire (c'est à cela qu'elle sert).

8voto

Nikolai N Fetissov Points 52093

Voici votre fuite :

while (1==1) {
    std::list<Line> *sl = new std::list<Line>;
    Line *s = new Line();
    sl->push_back(*s);
    //sl->pop_back(); //does delete the Line-Element
    sl->clear();
    delete sl;
}

Les collections STL stockent des éléments par valeur en lui allouant et en lui libérant de l'espace. Qu'est-ce que vous alloué vous doivent être libérés explicitement. Il suffit d'ajouter delete s à la fin de la boucle.

Si vous doivent stocker des pointeurs, envisagez de stocker des pointeurs gérés tels que boost::shared_ptr ou regardez dans Bibliothèque de conteneurs de pointeurs Boost .

En y regardant de plus près, vous n'avez pas besoin d'allouer Line sur le tas du tout. Changez-le juste en :

sl->push_back(Line());

Et, comme d'autres l'ont noté, assurez-vous Line Les pointeurs de l'utilisateur sont correctement gérés dans le constructeur de copie, l'affectation de copie et le destructeur.

2voto

nos Points 102226
    std::list<Line> *sl = new std::list<Line>;
    Line *s = new Line();
    sl->push_back(*s);
    //sl->pop_back(); //does delete the Line-Element
    sl->clear();
    delete sl;

Vous avez oublié de supprimer s . Vous l'avez modifié, vous devez le supprimer. Comme vous copiez des objets (en les plaçant dans une liste) tout en gérant la mémoire dans votre classe Line, vous devez également fournir un constructeur de copie et un opérateur d'affectation pour votre classe Line.

2voto

Fish Points 239

D'autres ont abordé spécifiquement la raison pour laquelle vous avez votre fuite - la suppression d'une liste de pointeurs ne supprime pas les objets qui sont pointés, et ne devrait pas le faire puisqu'un simple pointeur ne donne aucune indication s'il était la seule référence à cet objet), mais il y a d'autres moyens que de s'assurer que vous itérez la liste lors de la suppression pour supprimer les pointeurs.


En ce qui concerne l'exemple ici, il n'y a aucune raison d'utiliser des pointeurs sur quoi que ce soit, puisque vous les utilisez lorsqu'ils entrent dans le champ d'application et les abandonnez lorsqu'ils quittent le champ d'application - créez simplement tout sur la pile à la place et le compilateur se débarrassera correctement de tout en sortant du champ d'application. Par exemple.

while (1==1) {
    std::list<std::string> sl;
    std::string s = std::string("XXXXXXXXXXXXXXXXXXXXXXX");
    sl.push_back(s);
}

Si vous avez besoin du comportement des pointeurs (pour éviter d'avoir à dupliquer des objets qui sont liés à de nombreuses choses, etc. etc.), vous devriez jeter un coup d'œil aux pointeurs intelligents, car ils éliminent de nombreux pièges en gérant automatiquement le comptage des références et la sémantique dont vous avez besoin. (Plus précisément Jetez un coup d'œil aux pointeurs intelligents de boost )

Il existe de nombreux types de pointeurs intelligents que vous pouvez utiliser en fonction de vos besoins spécifiques et de la sémantique de propriété à représenter.

Le std::auto_ptr a une propriété stricte - si le pointeur est "copié" l'original est annulé et la propriété transférée - il n'y a jamais qu'un seul auto_ptr valide pour l'objet. L'objet pointé est supprimé chaque fois que le pointeur intelligent avec la propriété sort de la portée.

Il existe également des pointeurs partagés boostés et des pointeurs faibles qui utilisent le comptage de référence pour savoir quand libérer l'objet pointé. Avec les pointeurs partagés, chaque copie du pointeur augmente le nombre de références, et l'objet pointé est supprimé lorsque tous les pointeurs partagés sont hors de portée. Un pointeur faible pointe vers un objet géré par un pointeur partagé mais n'augmente pas le nombre de références. Si tous les pointeurs partagés parents sont supprimés, toute tentative de déréférencement d'un pointeur faible entraînera une exception facilement rattrapable.

Il est évident que la gamme des pointeurs intelligents ne s'arrête pas là, mais je vous conseille vivement d'y jeter un coup d'œil en tant que solution pour vous aider à gérer votre mémoire.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X