IdentifiantMot de passe
Loading...
Mot de passe oubli� ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les r�ponses en temps r�el, voter pour les messages, poser vos propres questions et recevoir la newsletter

C++ Discussion :

D�sallocation m�moire C++


Sujet :

C++

Vue hybride

Message pr�c�dent Message pr�c�dent   Message suivant Message suivant
  1. #1
    Membre averti
    Homme Profil pro
    �tudiant
    Inscrit en
    Juillet 2011
    Messages
    16
    D�tails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activit� : �tudiant

    Informations forums :
    Inscription : Juillet 2011
    Messages : 16
    Par d�faut D�sallocation m�moire C++
    Bonjour � tous,

    Je me permets de vous contacter car j'ai une question � vous poser concernant l'allocation et la d�sallocation en C++

    Je dispose d'une classe basique :

    Code : S�lectionner tout - Visualiser dans une fen�tre � part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    class Test
    {
     
    	private:
     
    	    char * pointeur;
     
    	public:
     
    	    Test( const char * chaine )
    	    {
    	        pointeur = new char[ 10 ];
    	        strcpy( pointeur, chaine );
    	    }
     
    	    ~Test()
    	    {
    	        delete[] pointeur;
    	        pointeur = NULL;
    	    }
     
    	    void show()
    	    {
    	    	std::cout << pointeur <<std::endl;
    	    }
    };
    et ma fonction main :

    Code : S�lectionner tout - Visualiser dans une fen�tre � part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    int main()
    {
    	Toto t1("ooo"); 
    	Toto t2("aaaa");
    	Toto t3 = t2;
     
    	t3.show();
     
    	return 1;
    }
    L'erreur que j'obtiens dans la console est la suivante :

    a.out(6947) malloc: *** error for object 0x10de008d0: pointer being freed was not allocated
    *** set a breakpoint in malloc_error_break to debug


    Mon erreur provient du destructeur quand je fais delete[] pointeur;

    Je ne comprends pas pourquoi alors que j'ai bien fais l'allocation � l'aide de new[] utiliser delete[] devrait donc �tre la solution...

    Merci pour votre aide :/

    MacInTouch.

  2. #2
    Membre �m�rite
    Homme Profil pro
    R&D imagerie 3D / prog embarqu�e
    Inscrit en
    Mars 2007
    Messages
    419
    D�tails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations professionnelles :
    Activit� : R&D imagerie 3D / prog embarqu�e
    Secteur : Sant�

    Informations forums :
    Inscription : Mars 2007
    Messages : 419
    Par d�faut
    Salut,

    Bon, je suppose que les Toto de ton main, sont en fait des Test.

    Tu as un bug parce que tu n'as pas d'op�rateur de copie explicite pour g�rer ton pointer.
    � la ligne 5 de ton main tu copie t2 dans t3. Le pointer de t3 et de t2 pointent donc au m�me endroit: le tableau de char allou� par t2.
    Disons que t2 est d�truit en premier il d�salloue le tableau. Lorsque t3 est � son tour d�truit, il essaye lui aussi de d�sallouer le tableau, mais comme c'est d�j� fait, tu plante

    Tu dois d�clarer un op�rateur de copie explicite pour que chaque instance de Test ait son propre tableau. Dans cet op�rateur, tu alloues un tableau de la m�me taille que celui de l'instance � copier, puis tu copie le contenu.

  3. #3
    Expert �minent

    Femme Profil pro
    Ing�nieur d�veloppement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    D�tails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (�le de France)

    Informations professionnelles :
    Activit� : Ing�nieur d�veloppement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 202
    Par d�faut
    Lis donc la faq sur les formes canonique (celle-ci et les suivantes)

    Tu y liras, entre autre, que chaque fois que tu �cris un destructeur non trivial pour un type T, il faut g�rer les constructeurs, la construction par copie ( T(const T&) ) et l'assignation (operator=).

  4. #4
    Membre exp�riment�

    Profil pro
    Inscrit en
    Mai 2005
    Messages
    264
    D�tails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2005
    Messages : 264
    Par d�faut
    Bonjour,

    � la ligne 5, tu cr�es un objet t3 comme une copie de t2. Le probl�me est que tu ne d�finis pas de constructeur de copie dans ta classe Test. Le compilateur en cr�e donc un pour toi, qui se contente de copier b�tement t2.pointeur dans t3.pointeur.

    Tu as donc deux objets dont les membres pointeur pointent sur la m�me chaine de caract�res.

    � la fin de ta fonction, tes objets sont d�truits dans l'ordre inverse de leur cr�ation. t3 est donc d�truit en premier et son constructeur lib�re la m�moire point�e par t3.pointeur. Ensuite, c'est au tour de t2 d'�tre d�truit. Probl�me : t2.pointeur pointe vers une adresse qui a d�j� �t� lib�r�e par le destructeur de t3.

    Appeler delete[] sur quelque chose qui ne pointe plus sur une donn�e allou�e par new[] est une erreur qui peut provoquer des bugs sympatiques comme une corruption m�moire. C'est � �viter absolument.

  5. #5
    Expert �minent

    Femme Profil pro
    Ing�nieur d�veloppement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    D�tails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (�le de France)

    Informations professionnelles :
    Activit� : Ing�nieur d�veloppement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 202
    Par d�faut
    ce genre de probl�matique est d'ailleurs la raison m�me de l'existance des pointeurs intelligents (smart pointers), dans boost et maintenant dans la norme STL (depuis C++11)

  6. #6
    Expert �minent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    D�tails du profil
    Informations personnelles :
    Sexe : Homme
    �ge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activit� : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par d�faut
    Salut,

    C'est, effectivement, un probl�me classique, dans le sens o� un pointeur est une variable (non sign�e) num�rique "comme les autres", qui peut etre copi�e et assign�e, mais qui repr�sente... l'adresse m�moire � laquelle on va trouver une information.
    L'une des formes canoniques de Coplien nous indique que chaque classe doit disposer de quatre fonction particuli�res:
    1. Le constructeur
    2. Le constructeur par copie
    3. L'op�rateur d'affectation " = "
    4. Le destructeur
    Si l'on ne donne pas de raison au compilateur de ne pas le faire en les d�finissant nous m�me, il va implicitement d�finir ces fonctions sous une forme publique, inline, non virtuelle (pour le destructeur, les autres ne pouvant pas �tre virtuels ) avec des comportements de base :

    Ainsi, il produira:
    1. Un constructeur public ne prenant pas d'argument et appelant les (pseudo) constructeurs sans arguments des membre de la classe dans l'ordre de leur d�claration si tu ne fournis aucun un constructeur
    2. Un constructeur public par copie (prenant une r�f�rence constante sur ton type) utilisant les (pseudo) constructeurs par copie des membres de la classe dans l'ordre de leur d�claration
    3. un op�rateur d'affectation public, qui renverra une copie de la r�f�rence constante pass�e en param�tre sous la forme d'une r�f�rence
    4. un destructeur non virtuel et public qui ne fait rien (qui se contente du comportement de destruction des membres de la classe dans l'ordre inverse de leur d�claration, comme pour toute variable d�clar�e "sur la pile")
    Le probl�me, avec ces comportements "de base", c'est que, si ta classe a un pointeur comme membre, le constructeur par copie va, tout simplement, copier la valeur num�rique (non sign�e) du pointeur.

    Cela signifie que tu auras deux objets "distincts" dont le membre pointe vers... une adresse m�moire identique

    Du coup, voici � peut pr�s ce qui se passe avec ton code quand il a la forme de:
    Code : S�lectionner tout - Visualiser dans une fen�tre � part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    int main()
    {
    	Toto t1("ooo"); // pas de problème, le constructeur est appelé,t1.pointeur est alloué avec new[] 
    	Toto t2("aaaa");// pas de problème, le constructeur est appelé,t2.pointeur est alloué avec new[] 
    	Toto t3 = t2; // CRACK seule l'adresse de la mémoire représentée
                          // par t2.pointeur est copiée dans t3.pointeur
     
    	t3.show();
     
    	return 1;
    } // 1- le destructeur de t3 est appelé (CRAAACK) : il invoque delete sur t3.pointeur
      // 2- le destructeur de t2 est appelé : BOOOUM : il invoque delete sur
      //    t2.pointeur qui correspond à l'adresse mémoire qui a déjà été libérée
      //    lors de la destruction de t3
    Il faut donc veiller � ce que la copie fasse une "copie en profondeur" du pointeur.

    Comprends par l� qu'il faut que la copie:
    1. Alloue un espace m�moire suffisant pour contenir l'ensemble des �l�ments de l'objet copi�.
    2. copie effectivement l'ensemble des �l�ments de l'objet copi� dans l'objet de destination.
    sous une forme (temporaire) qui pourrait etre proche de
    Code : S�lectionner tout - Visualiser dans une fen�tre � part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    class Test
    {
     
    	private:
     
    	    char * pointeur;
     
    	public:
     
    	    Test( const char * chaine )
    	    {
    	        pointeur = new char[ 10 ];
    	        strcpy( pointeur, chaine );
    	    }
                Test(const Test & rhs)
                {
                    pointeur = new char[10];
                    strcoy(pointeur, rhs.pointer);
                }
    	    ~Test()
    	    {
    	        delete[] pointeur;
    	        pointeur = NULL;
    	    }
     
    	    void show()
    	    {
    	    	std::cout << pointeur <<std::endl;
    	    }
    };
    Mais ce ne sera pas suffisant, car le probl�me viendrait avec un code proche de
    Code : S�lectionner tout - Visualiser dans une fen�tre � part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    int main()
    {
    	Test t1("ooo"); // pas de problème, le constructeur est appelé,t1.pointeur est alloué avec new[] 
    	Test t2("aaaa");// pas de problème, le constructeur est appelé,t2.pointeur est alloué avec new[] 
    	Test t3 = t2; // Plus de problème ici, t3.pointeur pointe sur une adresse mémoire différente de t2.pointeur
             t1 = t2; // Gros problème ici : t1.pointeur est alloué deux fois:
                      // 1- lors de la déclaration de t1
                      // 2- lors de la copie de t2 dans t1 qui nous fait perdre la valeur originelle de t1.pointeur ==> fuite mémoire
     
    	t3.show();
     
    	return 1;
    }
    Il faut donc veiller � ce que l'affectation �vite la fuite m�moire, et, pour cela, rien ne vaut l'idiome "copy and swap".

    En effet, on a la certitude, si l'on cr�e un objet de type Test, que la m�moire allou�e � son membre "pointeur" sera correctement lib�r�e lorsque l'objet est d�truit.

    On a aussi la certitude que, si l'on provoque la copie d'un objet de type Test, la copie est "correcte", vu qu'on vient de d�finir le constructeur par copie qui a le comportement ad�quat

    On sait enfin (c'est la norme qui nous l'assure ) que, si l'on cr�e une variable(quelle qu'elle soit) "sur la pile", son destructeur est automatiquement appel� lorsque l'on quitte la port�e dans laquelle elle a �t� d�clar�e.

    L'id�e est donc que, si l'on cr�e, dans l'op�rateur d'affectation, une variable de type Test (selon l'exemple), elle sera d�truite automatiquement lorsque l'on sortira de l'op�rateur d'affectation.

    Si l'on s'arrange pour intervertir tous les membres de la copie avec tous les membres de l'�l�ment sur lequel on travaille, l'�l�ment sur lequel on travaille repr�sentera, effectivement, toutes les valeurs "d'origine" de la copie et ce sont... les membres "d'origine" du membre sur lequel on travaille qui seront d�truit lorsque la copie sera automatiquement d�truite

    Ce comportement d'interversion pourrait �tre "na�vement" produit sous une forme proche de
    Code : S�lectionner tout - Visualiser dans une fen�tre � part
    1
    2
    3
    char * temp = copie.pointeur;
    copie.pointeur = origine.pointeur;
    origine.pointeur = temp;
    et s'appelle, en anglais, un "swap".

    Pour garder des fonctions aussi simples que possibles (dans le cas pr�sent, ca va, il n'y a qu'un membre � "swaper", mais il y a souvent bien plus de membres ), on va donc cr�er une nouvelle fonction (qui peut etre priv�e, car "� usage interne uniquement") "swap" qui fera le travail

    Mais, comme le d�veloppeur informatique est paresseux de nature, je te propose d'utiliser un comportement fournis par le standard qui fait exactement ce que l'on veut : la fonction swap, disponible (comme tout ce qui vient du standard) dans l'espace de noms std par simple inclusion du fichier d'en-t�te <algorithm>.

    Au final, ta classe Test ressemblerait � quelque chose comme
    Code : S�lectionner tout - Visualiser dans une fen�tre � part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    #include <algorithm> // pour disposer de std::swap
    class Test
    {
    	private:
    	    char * pointeur;
                void swap(Test & copie)
                {
                    // à faire pour tous les membres de Test ;)
                    std::swap(this.pointeur, copie.pointeur); // intervertit le membre
                }
    	public:
     
    	    Test( const char * chaine )
    	    {
    	        pointeur = new char[ 10 ];
    	        strcpy( pointeur, chaine );
    	    }
                Test(const Test & rhs)
                {
                    pointeur = new char[10];
                    strcoy(pointeur, rhs.pointer);
                }
                Test & operator=(Test const & rhs)
                {
                    Test copie(rhs); // crée une copie non constante de rhs
                    swap(rhs); //voir plus bas
                    return *this; // renvoie l'objet courent sous la forme d'une référence 
                } // copie est détruit ici
    	    ~Test()
    	    {
    	        delete[] pointeur;
    	        pointeur = NULL;
    	    }
    	    void show()
    	    {
    	    	std::cout << pointeur <<std::endl;
    	    }
    };
    Et l�, tu n'auras plus aucun probl�me avec ta classe Test (du moins, tant que tu te limiteras � 9 caract�res + le '\0'): tu pourras en cr�er autant que tu veux, les r�assigner dans tous les sens, tu n'auras plus ni fuite m�moire ni tentative de double lib�ration de la m�moire

    Un petit d�tail pour terminer...

    Pour indiquer qu'un programme s'est correctement termin�, la fonction main renvoie la valeur 0 et non 1
    A m�diter: La solution la plus simple est toujours la moins compliqu�e
    Ce qui se con�oit bien s'�nonce clairement, et les mots pour le dire vous viennent ais�ment. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 f�vrier 2014
    mon tout nouveau blog

  7. #7
    Inactif  


    Homme Profil pro
    Inscrit en
    Novembre 2008
    Messages
    5 288
    D�tails du profil
    Informations personnelles :
    Sexe : Homme
    �ge : 49
    Localisation : France, Rh�ne (Rh�ne Alpes)

    Informations professionnelles :
    Secteur : Sant�

    Informations forums :
    Inscription : Novembre 2008
    Messages : 5 288
    Par d�faut
    Citation Envoy� par leternel Voir le message
    ce genre de probl�matique est d'ailleurs la raison m�me de l'existance des pointeurs intelligents (smart pointers), dans boost et maintenant dans la norme STL (depuis C++11)
    Petite pr�cision (pour ceux qui lisent, pas pour toi leternel ) :
    "l'une des raisons". Une autre raison est d'avoir un code sur en cas d'exception (voir FAQ RAII et G�rer ses ressources de mani�re robuste en C++)

+ R�pondre � la discussion
Cette discussion est r�solue.

Discussions similaires

  1. Thread et d�sallocation m�moire
    Par plumedesiles dans le forum Linux
    R�ponses: 0
    Dernier message: 22/04/2010, 03h32
  2. d�sallocation m�moire - fonction - structure - tableau dynamique
    Par Flaherty Mc Coillean dans le forum D�buter
    R�ponses: 2
    Dernier message: 25/11/2009, 17h42
  3. d�sallocation m�moire et kernel panic
    Par flo-1987 dans le forum Linux
    R�ponses: 4
    Dernier message: 17/09/2009, 12h49
  4. Allocation d�sallocation m�moire
    Par Jahjouh dans le forum C++
    R�ponses: 5
    Dernier message: 02/04/2008, 04h09
  5. D�sallocation m�moire des types record
    Par mounis dans le forum Langage
    R�ponses: 2
    Dernier message: 07/02/2006, 13h21

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo