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 :

delete de pointeur


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre averti
    Profil pro
    Inscrit en
    Août 2010
    Messages
    18
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2010
    Messages : 18
    Par défaut delete de pointeur
    Bonjour à tous,

    Je suis débutant en C++ et j'ai une question au sujet de la commande delete des pointeurs.

    Ma question est la suivante. Lorsque l'on effectue un delete, on libère la mémoire référencée par ce pointeur qui peut donc à nouveau être utilisée. Le pointeur pointe toujours sur la case mémoire libérée mais toute commande qui accède au contenu de la mémoire est refusée par le compilateur

    ex:

    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
    // allocation dynamique d'un tableau de trois entiers 
     
    int * tab = new int [3] ;
     
    // initialisation du tableau
     
    for (int i(0); i < 3 ; i++)
    {
     *(tab + i) = 2;
    }
     
    // liberation de la memoire
     
    delete tab;
     
    cout << tab << endl ; // OK : contient l'adresse du premier entier du tableau
     
    cout << *tab << endl ; // NOK
    J'ai lu sur un site que cette situation était risquée, dans la mesure ou la mémoire étant libérée et le pointeur tableau pointant toujours sur la mémoire, il fallait affecter le pointeur à 0 pour éviter tout problème ( avec l'instruction tab(0); )


    Mais étant donné que je ne peux accéder aux valeurs par le pointeur après delete (cf NOK), j'ai du mal à voir où se situe le risque.

    Merci de vos lumières.

    Romain.

  2. #2
    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
    Citation Envoyé par Romain227 Voir le message
    Bonjour à tous,

    Je suis débutant en C++
    Ca a été le cas de tous
    et j'ai une question au sujet de la commande delete des pointeurs.
    parle plutot d'instruction, c'est plus juste Mais les pointeurs sont souvent la bête noir des débutants

    Ma question est la suivante. Lorsque l'on effectue un delete, on libère la mémoire référencée par ce pointeur qui peut donc à nouveau être utilisée.
    C'est le principe, en effet...
    Le pointeur pointe toujours sur la case mémoire libérée
    C'est en fait un peu plus complexe... et il me semble utile de préciser, pour être sur que tu comprenne la suite

    Un pointeur est une variable numérique qui contient l'adresse mémoire à laquelle on trouvera (normalement) la donnée du type attendu.

    Après un delete, la mémoire correspondante (à la variable du type attendu) est effectivement libérée, mais la valeur de l'adresse que l'on trouve dans le pointeur elle, n'a pas changé.

    On ne peut donc absolument pas donner la moindre certitude quant à ce que l'on va trouver à cette adresse mémoire : *peut etre* (si on a un peu de chance) retrouverons nous les données que l'on y avait mise, mais peut accéderons nous à de toutes nouvelles données...

    C'est ce que l'on appelle un "comportement indéfini" (on n'a aucun moyen de savoir ce qui pourra se passer si on accède au contenu de la mémoire qui se trouve à cette adresse)
    mais toute commande qui accède au contenu de la mémoire est refusée par le compilateur

    ex:

    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
    // allocation dynamique d'un tableau de trois entiers 
     
    int * tab = new int [3] ;
     
    // initialisation du tableau
     
    for (int i(0); i < 3 ; i++)
    {
     *(tab + i) = 2;
    }
     
    // liberation de la memoire
     
    delete tab;
     
    cout << tab << endl ; // OK : contient l'adresse du premier entier du tableau
     
    cout << *tab << endl ; // NOK
    As tu essayé de compiler cet exemple (en le plaçant, par exemple, dans les accolades de la fonction main)

    La surprise du chef, c'est que cela compile absolument sans problème!!!

    Le compilateur n'a en effet absolument pas l'intelligence pour se rendre compte qu'il ne peut pas accepter ce code qui est, syntaxiquement parlant, tout à fait valide.

    Par contre, le code a beau "sembler correct", il est particulièrement risqué d'essayer de l'exécuter, pare que nous nous confronterions à ce fameux comportement indéfini dont je viens de parler:

    dans le meilleur des cas, tu auras "simplement" une sortie aberrante (chez moi, j'obtiens... 8482800... mais si j'essaye demain, je pourrais très bien avoir autre chose )...

    Dans le pire des cas, cela pourrait provoquer le plantage complet de l'ordinateur, voir transmettre un ordre de lancer des missiles intercontinentaux sur un réseau hyper secret (bon, là, j'admets, j'exagère un tout petit peu )
    J'ai lu sur un site que cette situation était risquée, dans la mesure ou la mémoire étant libérée et le pointeur tableau pointant toujours sur la mémoire, il fallait affecter le pointeur à 0 pour éviter tout problème ( avec l'instruction tab(0); )
    Non pas avec tab(0), mais avec tab=0; ou, de manière plus explicite avec tab=NULL ( tab(0) est éventuellement admis, mais uniquement lors de la déclaration de la variable tab )
    Mais étant donné que je ne peux accéder aux valeurs par le pointeur après delete (cf NOK), j'ai du mal à voir où se situe le risque.
    Justement, c'est là qu'est l'astuce : tu ne peux pas (comprend : il ne faut pas que tu essaye d') y accéder parce qu'il y a un risque à le faire, mais les outils dont on dispose n'ont aucun moyen de t'en empêcher, et accepteront sans rechigner le fait que tu essaye de le faire...

    Il faut donc, veiller, lorsque tu travailles avec des pointeurs, que tu leur donnes une valeur que tu pourras identifier comme étant invalide, que ce soit avant que tu ne leur affecte une adresse valide (avant de leur affecter le résultat de new)ou après avoir invalidé l'adresse qui leur est affectée (après avoir libéré la mémoire avec delete).

    La seule adresse que tu peux identifier comme étant invalide sans risque de te tromper, c'est.... l'adresse 0 (ou NULL) qui, de plus, est évaluée à false lorsqu'il y a conversion implicite en booléen.

    Si tu respecte cette recommandation (faite avec moultes insistance ), il te sera possible (et nous t'encourageons grandement à le faire ) de vérifier si l'adresse pointée par le pointeur est bel et bien valide avant toute tentative d'accéder à "ce qui est pointé par le pointeur"
    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

  3. #3
    Membre averti
    Profil pro
    Inscrit en
    Août 2010
    Messages
    18
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2010
    Messages : 18
    Par défaut
    Bonjour,

    ton explication est très claire, merci beaucoup. L'idée à retenir est donc d'affecter le pointeur à une adresse non valide après un delete pour couper l'accès à la mémoire libérée, et ce en dépit du fait que le compilateur le tolère.

    Bonne journée.

    Romain

  4. #4
    Membre éprouvé
    Profil pro
    Inscrit en
    Novembre 2004
    Messages
    2 766
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2004
    Messages : 2 766
    Par défaut
    Le rôle de l'affectation à 0, NULL, ou nullptr, est de pouvoir effectuer un test sur la valeur du pointeur.

    Si au moment du test, on suppose que tout delete est suivi d'une affectation à 0, on sait ce qu'il en est quant à la validité du pointeur.

    Si on ne fait pas cette affectation, on peut toujours tester, mais on peut difficilement en déduire quelque chose...

  5. #5
    Membre émérite Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Par défaut
    Bonjour,

    Le fait d'affecter NULL à un pointeur qui ne pointe pas/plus sur une zone mémoire allouée a un autre intérêt.
    Cela permet d'éviter de tenter de libérer plusieurs fois une même zone mémoire.

    Dis comme ça, ça peut paraître idiot.
    Mais ce cas peut tout de même arriver, et de manière assez vicieuse, donc difficile à contrôler.

    Par exemple, dans une boucle, on peut revenir plusieurs fois au même endroit sans s'en rendre compte.
    Ou alors si la libération d'une ressource peut avoir lieu dans plusieurs endroits du programme.

    Puisqu'un delete sur NULL est parfaitement valide (voir la F.A.Q.), on gagne en sûreté.

  6. #6
    Membre très actif
    Profil pro
    professeur des universités à la retraite
    Inscrit en
    Août 2008
    Messages
    364
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : professeur des universités à la retraite

    Informations forums :
    Inscription : Août 2008
    Messages : 364
    Par défaut
    Noter qu'en C++2011 on affecterait dans ce cas le pointeur invalide plutôt à
    nullptr
    qu'à
    NULL

  7. #7
    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
    Citation Envoyé par ptyxs Voir le message
    Noter qu'en C++2011 on affecterait dans ce cas le pointeur invalide plutôt à
    nullptr
    qu'à
    NULL
    Oui, mais C++2011 vient "à peine" d'être voté, et son support actuel est encore particulièrement parcellaire (tous compilateurs confondus).

    C'est à tel point que le support de cette nouvelle norme doit être explicitement demandé à la compilation, et que c'est la norme de 2003 qui est supportée par défaut!

    On peut donc citer le fait que, bientôt, nous disposerons d'un terme clairement explicite, mais je ne suis pas sur que ce soit primordial dans le cadre de cette discussion, où l'on essaye déjà d'expliquer "simplement" le fonctionnement et les dangers des pointeurs à quelqu'un qui débute

    "Tout vient à point à qui sait attendre"
    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

  8. #8
    Membre averti
    Inscrit en
    Juin 2011
    Messages
    45
    Détails du profil
    Informations forums :
    Inscription : Juin 2011
    Messages : 45
    Par défaut
    Bonjour,

    Je me permet de réactiver ce sujet car je me pose une question sur l'allocation dynamique :

    Par hasard, je me suis rendu compte qu'après avoir fait un delete sur un objet, et un new juste après pour créer un objet de même classe, l'adresse alouer était exactement la même que l'adresse du premier objet. De cette façon, je pouvait utiliser le pointeur qui a fait l'objet du delete sans problème. Un exemple en code :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
        Objet *adr;
        Objet *bdr;
        adr=new Objet();
     
        delete adr;
     
        bdr=new Objet();
     
        adr->fonction();
    L'appel de la fonction de Objet fonctionne parfaitement. Mais il n'y a qu'un seul objet pointé à la fois par adr et bdr.

    Est-ce que cela est normale ou seulement le fait du hasard que la création d'un nouvel objet renvoie exactement la même adresse qu'un objet supprimer juste avant ??

    Est-que l'on peut se servir de cette particularité sans risque ? Par exemple en voulant réinitialiser un objet par un delete puis un new sans changer son adresse ...

  9. #9
    Expert confirmé

    Inscrit en
    Novembre 2005
    Messages
    5 145
    Détails du profil
    Informations forums :
    Inscription : Novembre 2005
    Messages : 5 145
    Par défaut
    Citation Envoyé par vinceouille Voir le message
    Bonjour,

    Je me permet de réactiver ce sujet car je me pose une question sur l'allocation dynamique :

    Par hasard, je me suis rendu compte qu'après avoir fait un delete sur un objet, et un new juste après pour créer un objet de même classe, l'adresse alouer était exactement la même que l'adresse du premier objet. De cette façon, je pouvait utiliser le pointeur qui a fait l'objet du delete sans problème. Un exemple en code :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
        Objet *adr;
        Objet *bdr;
        adr=new Objet();
     
        delete adr;
     
        bdr=new Objet();
     
        adr->fonction();
    L'appel de la fonction de Objet fonctionne parfaitement. Mais il n'y a qu'un seul objet pointé à la fois par adr et bdr.

    Est-ce que cela est normale ou seulement le fait du hasard que la création d'un nouvel objet renvoie exactement la même adresse qu'un objet supprimer juste avant ??

    Est-que l'on peut se servir de cette particularité sans risque ? Par exemple en voulant réinitialiser un objet par un delete puis un new sans changer son adresse ...
    Pur hasard. Alloue deux objets liberes-les, realloue un objet. Si tu as bien la meme adresse que le dernier, change l'ordre de liberation, je parie que tu n'auras plus la meme adresse.

  10. #10
    Membre averti
    Inscrit en
    Juin 2011
    Messages
    45
    Détails du profil
    Informations forums :
    Inscription : Juin 2011
    Messages : 45
    Par défaut
    Et bien de plus en plus étrange, avec ce petit changement :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
        Objet *adr;
        Objet *bdr;
        adr=new Objet();
        bdr=new Objet();
     
        delete bdr
        delete adr;
     
        bdr=new Objet();
     
        adr->fonction();
    la fonction marche toujours et adr et bdr pointe tout les deux sur la même adresse. J'ai suivi avec le debugger et le fait de realouer "bdr" affecte une adresse à "bdr" (ça c'est normale) mais également la même adresse à "adr" !!!

    Je précise que je travaille sur QtCreator et windows 7.

  11. #11
    Expert confirmé

    Inscrit en
    Novembre 2005
    Messages
    5 145
    Détails du profil
    Informations forums :
    Inscription : Novembre 2005
    Messages : 5 145
    Par défaut
    Faire delete ptr ne change pas la representation de ptr, ca la rend juste invalide.

  12. #12
    Membre émérite

    Profil pro
    Inscrit en
    Décembre 2008
    Messages
    533
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2008
    Messages : 533
    Par défaut
    Je ne vois pas ce qu'il y a de choquant à ce que la nouvelle adresse de bdr soit l'ancienne adresse de adr. Par contre, ce qui est choquant, c'est que tu ne mettes pas adr et bdr à NULL (ou nullptr) après l'appel des 2 delete.

  13. #13
    Expert confirmé

    Inscrit en
    Novembre 2005
    Messages
    5 145
    Détails du profil
    Informations forums :
    Inscription : Novembre 2005
    Messages : 5 145
    Par défaut
    Citation Envoyé par cob59 Voir le message
    Par contre, ce qui est choquant, c'est que tu ne mettes pas adr et bdr à NULL (ou nullptr) après l'appel des 2 delete.
    Pourquoi? Normalement ca sort immediatement du scope.

  14. #14
    Membre averti
    Inscrit en
    Juin 2011
    Messages
    45
    Détails du profil
    Informations forums :
    Inscription : Juin 2011
    Messages : 45
    Par défaut
    Rien ne me choque dans tout cela(il en faut plus ...), j'essaye juste de comprendre la logique, je prend conscience que tout ça n'est pas très rigoureux mais ça permet de mieux comprendre ce qui se passe dans la machine .. Je crois avoir compris (commentaire dans le code):

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
        Objet *adr;
        Objet *bdr;
        adr=new Objet();
        bdr=new Objet();
     
        delete bdr;
        delete adr;
     
        bdr=new Objet(); //aloue une adresse à "bdr" qui est "par hasard" la même que "adr" (mais le hasard se produit à chaque fois... )
     
        adr->fonction(); // le fait qu'un objet ait été aloué à l'ancienne adresse pointé par "adr" fait que adr, qui possèdent toujours la valeur  l'adresse initial, peut être utilisé.

    Par contre si on code :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
        Objet *adr;
        Objet *bdr;
        adr=new Objet();
        bdr=new Objet();
     
        delete bdr;
        delete adr;
        adr=NULL; //"efface" la valeur de l'adresse dans "adr"
     
        bdr=new Objet(); //aloue une adresse à "bdr" qui est "par hasard" (mais le hasard se produit à chaque fois) la même que l'ancienne "adr"
     
        adr->fonction(); // ne marche plus car adr ne contient plus d'adresse

    Je m'interroge juste sur ce hasard qui se produit à chaque qui affecte la même adresse. Cela se produira-t-il vraiment à chaque fois si on fait le new juste après le delete ?

    Edit : koala01 a répondu à mes interrogations. Merci beaucoup, je me sens moins idiot maintenant !!!!

  15. #15
    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
    il faut comprendre que l'adresse à laquelle va se trouver un objet que tu crées avec new va exclusivement dépendre des disponibilités du système, entre autres, au niveau de l'espace contigu dont il faut disposer en mémoire pour y placer l'ensemble des données.

    Bien sur, si tu viens d'indiquer au système qu'un espace X peut etre considéré comme disponible, il y a des chances, si tu demande au système de te fourni un espace mémoire identique, pour qu'il te renvoie l'espace mémoire que tu viens juste d'indiquer comme dispo, tout comme il y a cependant exactement le même nombre de chance pour qu'il t'envoie un espace mémoire contigu suffisant à n'importe quelle autre adresse.

    Il ne faut donc en aucun cas considérer, si tu écris un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    int main()
    {
        Type *ptr = new Type;
        /* ... */
        delete ptr;
        Type * other = new type;
    }
    que ptr et other contiendront la même adresse mémoire...

    Au mieux, avec beaucoup de chance, ce n'est qu'une particularité d'un compilateur particulier, qu'une liberté prise par celui-ci pour éviter d'avoir à relancer le processus, malgré tout relativement couteux, d'une allocation dynamique par le système.

    Tu prendrais donc un énorme risque à considérer ce comportement comme "normal"
    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

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Réponses: 5
    Dernier message: 11/06/2008, 23h52
  2. [delete] detruire un pointeur
    Par poukill dans le forum C++
    Réponses: 2
    Dernier message: 09/03/2007, 10h05
  3. delete [] et pointeur intelligent
    Par zenux dans le forum C++
    Réponses: 11
    Dernier message: 04/12/2006, 09h18
  4. tableau de pointeurs et new, delete
    Par luckydigit dans le forum C++
    Réponses: 12
    Dernier message: 21/07/2006, 11h24
  5. [Pointeur]plusieurs new, un seul delete ?
    Par snoop dans le forum C++
    Réponses: 4
    Dernier message: 18/07/2006, 18h33

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