Partage
  • Partager sur Facebook
  • Partager sur Twitter

Supression des pointeurs obligatoire

Est-ce obligatoire de supprimer les pointeurs ?

Anonyme
    2 avril 2021 à 23:18:24

    Bonjour, vu dans le titre, j'aimerais savoir si nous sommes obligés de supprimer les pointeurs en C++.

    Je ne sais pas non plus si ils sont automatiquement supprimés de la RAM à la fermeture du programme.

    Merci de m'aider 😁.

    • Partager sur Facebook
    • Partager sur Twitter
      3 avril 2021 à 1:28:51

      Réponse courte : comme je suppose que ta question est "faut il toujours appeler delete après new", la réponse est oui. Les régles (simples) sont :

      - delete doit être appelé exactement UNE fois. Si tu appelles delete plus d'une fois, c'est un pointeur invalide. Si tu ne l'appelles pas, c'est une fuite mémoire.

      - delete avec new et delete[] avec new[]

      --------------------------------------------------------------------------------

      Réponse plus longue (mais pas trop) : la mémoire allouée doit être libérée, mais cela ne veut pas dire que tu dois forcément le faire manuellement. Il existe des outils pour gérer automatiquement la mémoire et c'est ce qui est recommandé d'utiliser dans un vrai code. Par exemple std::vector pour les tableaux, std::unique_ptr pour les pointeurs.

      • Partager sur Facebook
      • Partager sur Twitter
      Anonyme
        3 avril 2021 à 3:22:25

        Merci 😀 ! Et maintenant une autre question :

        Dans le cours d'OC, ils disent de mettre "ptr = 0;" mais je ne le vois jamais dans les programmes en C++ sur Internet.

        Est-il obligatoire ?

        Et même, ça devrait être "ptr = nullptr;", non ?

        OC disait qu'il faut d'abord enlever la "flèche" puis supprimer la variable de la RAM, si mes souvenirs sont bons.

        Certains disent qu'il ne faut pas suivre ce cours...

        -
        Edité par Anonyme 3 avril 2021 à 3:37:01

        • Partager sur Facebook
        • Partager sur Twitter
          3 avril 2021 à 3:36:24

          Oui, ca devrait être nullptr. Dans les vieux codes C++, on utilise parfois les syntaxes hérité du C : 0 ou NULL. L'avantage de nullptr, c'est que c'est un type de pointeur et produira une erreur si tu utilises nullptr avec autre chose qu'un pointeur. 0 et NULL peuvent être utilisés avec autre chose que des pointeurs et donc tu risques de ne pas voir si ton code contient une erreur.

          Le cours n'a pas été mis à jour depuis des années.

          Et utiliser une variable non initialisée est un bug. Plus précisement un UB (undefined behavior = comportement indeterminé). Une chose importante qu'il faut savoir avec le C++, c'est qu'il est possible d'avoir un code qui est incorrect, mais qui compile sans erreur. Et dans ce cas, il est parfois très difficile de corriger un code. Un UB est un exemple de ce type de problème. Pour éviter cela, la règle de bonne pratique est de toujours initialiser les variables (sauf si tu as une très très très bonne raison de ne pas le faire).

          • Partager sur Facebook
          • Partager sur Twitter
          Anonyme
            3 avril 2021 à 3:54:12

            Euh... Je parlais de mettre le pointeur à 0 après le delete...

            Et oui, un jour, j'ai crée une bool left, plus tard, j'ai fait left = !left et le compilateur m'a affiché un warning pour variable non initialisée.

            Et il faut le mettre à 0 (ou nullptr) avant ou après delete ?

            Dans le cours, j'ai vu après, mais moi, je le mets avant par cause de plantage.

            Je pense que c'est parce que j'ai fait "delete ptr", je ne peux pas faire "ptr = nullptr" puisqu'il est supprimé.

            -
            Edité par Anonyme 3 avril 2021 à 4:02:58

            • Partager sur Facebook
            • Partager sur Twitter
              3 avril 2021 à 4:20:49

              Si tu mets le pointeur à nul avant l'appel à delete, cela ne libérera pas la mémoire puisque qu'il n'y a pas de valeur à libérer. Si tu as un crash, c'est une mauvaise gestion de ta mémoire.

              Mettre à nul un pointeur libéré n'a de sens que si le pointeur est vérifié par la suite. Sinon c'est juste une instruction inutile que je considère plus proche de la programmation défensive qu'autre chose. La majorité du temps le pointeur n'est plus utilisé et le remettre à zéro ne sert à rien.

              Mais comme cela à été dit, on utilise pas new/delete directement, on passe par des outils qui les utilisent pour nous.

              • Partager sur Facebook
              • Partager sur Twitter
              Anonyme
                3 avril 2021 à 4:34:02

                jo_link_noir a écrit:

                La majorité du temps le pointeur n'est plus utilisé et le remettre à zéro ne sert à rien.

                Donc, si j'ai bien compris :

                int main()
                {
                     int i = 2;
                     int *ptr = &i;
                     
                     delete ptr;
                     ptr = 0; // Cette ligne est inutile ?
                
                     return 0;
                }


                • Partager sur Facebook
                • Partager sur Twitter
                  3 avril 2021 à 5:08:13

                  Le seul cas où mettre un pointeur à 0 après delete, qui me vienne à l'esprit, c'est pour l'écriture d'un opérateur d'affectation par copie qui ne propose pas une garantie forte de résistance aux exceptions à l'ancienne pour libérer avant d'allouer-- avec une garantie forte (où l'on alloue un futur buffer avant de libérer l'ancien buffer), nul besoin de ce genre de bidouille.

                  Ça et d'autres cas similaires où l'on bidouille avec la mémoire en croyant que l'on a une chance de s'en sortir alors que nous sommes dans un monde à exceptions...

                  J'essaie de réfléchir à des cas légitimes de delete hors destructeur qui pourrait justifier cela (en admettant que l'on ait des dangling pointers non maîtrisés) et ... ça ne me vient pas vraiment en fait.

                  Bref, embrasse le C++ moderne qui commence dans les années 90 avec le RAII et tous les conteneurs standards, et qui se démocratise en 2011 avec unique_ptr.

                  Accessoirement, un delete sur l'adresse d'une variable sur la pile est totalement illégal. Les problèmes commencent là.

                  -
                  Edité par lmghs 3 avril 2021 à 5:10:49

                  • Partager sur Facebook
                  • Partager sur Twitter
                  C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
                    3 avril 2021 à 7:30:39

                    MO9845 a écrit:

                    Donc, si j'ai bien compris :

                    int main()
                    {
                         int i = 2;
                         int *ptr = &i;
                         
                         delete ptr;
                         ptr = 0; // Cette ligne est inutile ?
                    
                         return 0;
                    }

                    Relis les 2 règles que j'ai donné avant. Un delete pour un new. Où est le new dans ton code ?

                    Tu viens juste d'expérimenter par toi même le très gros problème des pointeurs : des règles ultra simples et pourtant il est très facile d'écrire un code invalide.

                    Et ce n'est pas toi. Même les experts peuvent faire des erreurs avec les pointeurs. Pour ça que dans les vrais codes, on évite d'utiliser des pointeurs bruts comme ça et on va préférer les outils qui apportent des garanties dans le code (vector, unique_ptr, etc).

                    -------------------------------------------------------------

                    Et c'est normal que tu n'as pas tout compris cette histoire de où mettre les delete, les new, les nullptr, etc. C'est le très gros défaut de la majorité des cours de C et de C++ : ils expliquent comment utiliser les syntaxes, mais ils n'expliquent pas comment les utiliser correctement.

                    Je te ferais un petit résumé demain sur les concepts a comprendre sur les pointeurs.

                    -----------------------------------------------------------

                    lmghs a écrit:

                    exceptions

                    Je sais qu'on prend souvent l'exemple des exceptions pour expliquer en quoi les pointeurs posent problème en C++, mais a mon sens, c'est pas une bonne idée.

                    Les pointers bruts posent problème. Avec ou sans exception. En C ou en C++ avec les exceptions désactivées (par exemple dans Qt), les pointeurs posent problème.

                    Revenir à l'exemple des exceptions systématiquement, c'est a mon sens un problème parce que cela ne permet pas aux apprenants de comprendre la problématique profonde des pointeurs (qui est qu'ils n'ont pas de sémantique qui apporte des garanties sur la validité du code. Exception ou non).

                    -
                    Edité par gbdivers 3 avril 2021 à 7:30:54

                    • Partager sur Facebook
                    • Partager sur Twitter
                    Anonyme
                      3 avril 2021 à 9:55:58

                      gbdivers a écrit:

                      MO9845 a écrit:

                      Donc, si j'ai bien compris :

                      int main()
                      {
                           int i = 2;
                           int *ptr = &i;
                           
                           delete ptr;
                           ptr = 0; // Cette ligne est inutile ?
                      
                           return 0;
                      }

                      Où est le new dans ton code ?

                      Ah oui, mince :p...

                      Alors, ça, c'est bon ?

                      #include <iostream>
                      
                      using namespace std;
                      
                      int main()
                      {
                      	int *ptr = new int;
                      	*ptr = 5;
                      
                      	cout << *ptr << endl;
                      
                      	delete ptr;
                      
                      	return 0;
                      }
                      

                      J'ai beau avoir un très bon niveau de C++ et POO, les pointeurs me posent encore des problèmes.
                      En faite, j'ai peur de vider ma RAM en oubliant de supprimer mes pointeurs dans mes jeux.

                      J'ai déjà fait la grosse erreur ! J'avais oublié de les supprimer dans une classe qui était instanciée (très) souvent.



                      -
                      Edité par Anonyme 3 avril 2021 à 10:01:44

                      • Partager sur Facebook
                      • Partager sur Twitter
                        3 avril 2021 à 12:41:29

                        gbdivers, tu soulèves des problématiques intéressantes.

                        > Je sais qu'on prend souvent l'exemple des exceptions pour expliquer en quoi les pointeurs posent problème en C++, mais a mon sens, c'est pas une bonne idée.

                        "on" je ne sais pas, mais moi, oui quasi systématiquement. Je m'en sers façon preuve par l'absurde. "Ecrivons un code où l'on gère la mémoire à la main. Rajoutons une exception. Et là pouf on vois vite l'inmaintenabilité de la chose. Et la conclusion qui s'en suit: impossible de gérer la mémoire à la main en situation exceptionnelle, même pour des professionnels".

                        Historiquement, c'est grâce aux exceptions que l'on est entrés dans l'ère moderne, que l'on a commencé à réussir à dire: "mais arrêtez de gérer la mémoire à la main, vous n'y arriverez pas", et aux articles de H. Sutter sur l'exception safety dans ses GOTW publiés ensuite dans Exceptional C++. OK, j'avoue l'approche positive avec std::string a été plus efficace encore.

                        Alors certes, cela ne couvre pas les cas des "si c'est comme ça je n'utiliserai pas d'exceptions". Mon objectif est d’abattre le sentiment de confiance que les approches à la C suffisent en C++ pour faire rentrer dans le crane qu'en C++ il faut employer les bonnes pratiques du langage -- cas particulier de nos suffisances que quand on débarque dans un nouveau langage il suffit juste d'en apprendre la syntaxe. Arf.

                        Est-ce que présenter toutes les applications des pointeurs et leur mélange malaisé suffira à détourner du sentiment de confiance que dans C++, il y a C, et que "je suis bon en C et que je sais gérer les pointeurs"? Alors certes à un moment donné il va falloir le décrypter. Mais quel arguments employer avec les débutants en C++ qui pensent être au niveau des pointeurs? et quels arguments pour les suffisamment avancés avec qui on peut creuser la question?

                        > Alors ça c'est bon?

                        C'est bof.

                        Ça c'est plus robuste

                        #include <iostream>
                        #include <memory> 
                         
                        int main()
                        {
                            auto ptr = std::make_unique<int>();
                            *ptr = 5;
                        
                            // ou mieux
                            auto ptr = std::make_unique<int>(5);
                         
                            std::cout << *ptr << std::endl;
                         
                            return 0;
                        }

                        Et ça, c'est carrément mieux

                        #include <iostream>
                         
                        int main()
                        {
                            int i = 5;
                        
                            std::cout << i << std::endl;
                         
                            return 0;
                        }

                        Car la bonne pratique est de minimiser l'emploi des pointeurs quand il n'y en a pas besoin. Mais bon, pour le soucis de l'exemple on peut considérer que c'est une simplification d'un cas où l'on aurait vraiment besoin d'allouer. :)

                        • Partager sur Facebook
                        • Partager sur Twitter
                        C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
                        Anonyme
                          3 avril 2021 à 13:33:16

                          Ok, merci :) !

                          Dans quelle situation dois-je mettre "ptr = 0" ?

                          Est-ce utile de supprimer les pointeurs dans les paramètres d'une fonction ?

                          Est-ce que vider une collection hétérogène supprimera les pointeurs à l'intérieur (je ne pense pas :-°) ?

                          Je vais essayer de "m'auto-résumer" :

                          Delete doit être appelé autant de fois que new.

                          Mettre un pointeur à 0 (ou nullptr) n'est utile que lorsque sa valeur est l'adresse d'une autre variable (ex : int *ptr = &i).

                          J'ai juste ?

                          -
                          Edité par Anonyme 3 avril 2021 à 13:38:54

                          • Partager sur Facebook
                          • Partager sur Twitter
                            3 avril 2021 à 14:11:19

                            MO9845 a écrit:

                            a- Dans quelle situation dois-je mettre "ptr = 0" ?

                            b- Est-ce utile de supprimer les pointeurs dans les paramètres d'une fonction ?

                            c- Est-ce que vider une collection hétérogène supprimera les pointeurs à l'intérieur (je ne pense pas :-°) ?

                            Je vais essayer de "m'auto-résumer" :

                            d- Delete doit être appelé autant de fois que new.

                            e- Mettre un pointeur à 0 (ou nullptr) n'est utile que lorsque sa valeur est l'adresse d'une autre variable (ex : int *ptr = &i).

                            f- J'ai juste ?

                            -
                            Edité par MO9845 il y a 14 minutes


                            a- Réponse courte. Jamais. Réponse longue, seulement si le flot d'exécution est bordélique et que le pointeur est susceptible de se prendre à nouveau un delete.

                            b- `f(T*)`, C'est indécidable dans l'absolu. Tout dépend de ce à quoi sert le pointeur en vrai. Préférer:
                            - f(T const&); -- on donne à regarder quelque chose que l'on ne pourra pas altérer
                            - ou f(T&); -- on donne à regarder quelque chose que l'on pourra altérer
                            - ou f(std::unique_ptr<T>) (et quid de `f(std::unique_ptr<T[]>);`hein?) -- on confie la responsabilité d'une ressource allouée: la fonction est un puit qui devient responsable de la ressource
                            - ou f(std::vector<T> const&); (et variantes, voire f(std::span<T>) et f(std::span<T const>) -- on donne à regarder un tableau d'éléments contigus en mémoire -- altérable seulement si passé sans le const
                            - ou f(optional<T>) -- on donne, ou pas, un élément de type T (par valeur avec la version std::)
                            - voire f(std::string const&) (et variantes, dont f(std::string_view)) -- comme pour le tableau mais avec une chaine de caractères.

                            Ca sera bien plus clair quant aux intentions, aux possibilités, aux obligations, et cela sera infiniment plus robuste.

                            Même combat pour `T*f();`

                            c- Une collection hétérogène, ce n'est pas directement possible. J'imagine vu la thématique que tu veux parler des collections de pointeurs. De nouveau, `std::vector<T*>`, c'est la merde. D'où `std::vector<std::unique_ptr<T>>;`.

                            d- Oui, 0 de chaque est la bonne quantité. Et c'est totalement possible depuis la vieille norme de 2014 (7 ans tout de même maintenant!).

                            e- Non. Ca ne sert à rien. Tu n'as pas compris ce qu'est un pointeur si tu continues à vouloir détruire ce qui pointe sur une variable locale, sauf que et puis non, tu ne veux plus pointer sur la variable locale.

                            Un pointeur c'est un nombre entier qui correspond à une adresse en mémoire.

                            - Ce peut l'adresse d'un unique élément, ou l'adresse du premier élément d'une séquence contiguë en mémoire
                            - Ce peut être l'adresse d'un machin que l'on regarde seulement, ou l'adresse de quelque chose dont nous sommes responsable (si cela a été alloué sur le tas); et parfois le protocole de libération pourra encore être différent. Il faut libérer ce dont nous sommes responsable, ou confier la responsabilité à quelqu'un d'autre, et il ne faut surtout pas libérer ce dont nous ne sommes pas, ou plus, responsable.

                            f- Pas vraiment du coup ^^'

                            -
                            Edité par lmghs 3 avril 2021 à 14:24:48

                            • Partager sur Facebook
                            • Partager sur Twitter
                            C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
                              4 avril 2021 à 20:42:50

                              lmghs a écrit:

                              abattre le sentiment de confiance que les approches à la C

                              Mais en réalité, le problème n'est pas que les approches "à la C" ne marchent pas en C++. Elles ne marchent pas tout court. Même en C.

                              Il faut quand même se rappeler que beaucoup de bugs et de techniques de hacking (buffer overflow par exemple) viennent de problèmes de gestion de la mémoire.

                              Je n'ai rien personnellement contre le fait qu'un cours de C++ commence par les pointeurs bruts en soi. Mais le gros problème est que les cours qui font ça sont juste mauvais. Ils expliquent la syntaxe, mais pas comment utiliser correctement les pointeurs. Dans un monde de bisounours sans problème et sans erreur.

                              Se baser sur les exceptions pour expliquer le problème de la gestion manuelle de la mémoire en C++ est pas trop mal en soi. Il est simple et pédagogique. Mais cela laisse penser que le problème des pointeurs et de la gestion manuelle de la mémoire est spécifique au C++, alors que c'est un problème aussi en C.

                              Il est préférable à mon avis de corriger cette confiance envers les pointeurs qui a été transmise par les mauvais cours.

                              -
                              Edité par gbdivers 4 avril 2021 à 20:44:49

                              • Partager sur Facebook
                              • Partager sur Twitter
                              Anonyme
                                4 avril 2021 à 20:48:30

                                Donc si je résume toute cette discussion :

                                - Un new = un delete.

                                - ptr = nullptr; est inutile.

                                - C'est préférable d'utiliser une référence dans une fonction.

                                - Il faut se servir le minimum d'un pointeur.

                                -
                                Edité par Anonyme 4 avril 2021 à 20:53:30

                                • Partager sur Facebook
                                • Partager sur Twitter
                                  4 avril 2021 à 21:20:13

                                  Tu oublis le plus important: on n'utilise jamais new et delete, mais des pointeurs intelligents comme std::unique_ptr

                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                    4 avril 2021 à 21:49:15

                                    MO9845 a écrit:

                                    - ptr = nullptr; est inutile.

                                    Non.

                                    Je dois y aller, je reviendrai plus tard pour detailler.

                                    • Partager sur Facebook
                                    • Partager sur Twitter
                                    Anonyme
                                      4 avril 2021 à 21:52:09

                                      C'est parce qu'ils sont supprimés automatiquement ?

                                      Combien de fois j'ai écris "supprimés" dans ce forum :lol:...

                                      • Partager sur Facebook
                                      • Partager sur Twitter
                                        5 avril 2021 à 1:56:15

                                        Salut,

                                        MO9845 a écrit:

                                        - ptr = nullptr; est inutile.

                                        Non, enfin, pas forcément... Car la bonne question à se poser est "aurai-je encore accès au nom de mon pointeur après avoir appelé delete ?"

                                        Je m'explique:

                                        Imaginons deux fonctions différentes.  La première va prendre la forme de

                                        void foo(){
                                            Type * ptr = new Type; // le type de la donnée pointée
                                                                   // n'a que peu d'intérêt ici 
                                            /* je manipule l'adresse mémoire donnée par ptr; */
                                           delete ptr;
                                           /* il n'y a plus rien après le delete */
                                        }

                                        et la deuxième va prendre la forme de

                                        void bar(){
                                            Type * ptr = new Type; // le type de la donnée pointée
                                                                   // n'a que peu d'importance ici
                                            /* je manipule l'adresse mémoire donnée par ptr */
                                            delete ptr;
                                            /* j'ai encore d'autres choses à faire avant d'avoir fini
                                             * je risque donc de vouloir utiliser ptr qui, à ce 
                                             * stade,  représente une adresse mémoire invalide, 
                                             * car libérée
                                             */
                                        }

                                        Note que bar pourrait tout aussi bien prendre la forme ( sans doute plus claire) de

                                        void bar(){
                                            Type* ptr= new Type;
                                            /* je manipule l'adresse mémoire représentée par ptr */
                                            if(condtion ){
                                                delete ptr;
                                            }
                                            /* la suite du traitement */
                                        }

                                        hé bien, dans le cas de foo(), ptr devient purement et simplement inaccessible après l'instruction delete, car la prochaine instruction est l'accolade fermante qui représente la fin de la portée dans laquelle ptr est connu du compilateur.

                                        pour tout ce qui se trouve entre l'instruction delete

                                        Il ne sera donc pas indispensable de mettre ptr à nullptr, car on ne sait plus utiliser la donnée représentée par cet identifiant.

                                        Dans le cas de bar, par contre, l'identifiant ptr reste connu du compilateur pour tout ce qui  se trouve entre l'instruction delete et l'accolade fermante.  Seulement, ptr ne représente plus une donnée valide dans toute cette partie, vu que l'adresse mémoire que ptr représentait a été libérée.

                                        A ce moment là, il devient essentiel de donner la seule valeur connue pour représenter un pointeur invalde à notre donnée ptr: nullptr, car c'est la seule valeur que nous pourrons tester pour déterminer, dans tout ce qui sépare l'instruction delete de l'accolade fermante, si notre pointeur pointe vers une adresse valide (et peut donc être déréférencé) ou non.

                                        • Partager sur Facebook
                                        • Partager sur Twitter
                                        Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
                                          5 avril 2021 à 2:30:42

                                          MO9845 a écrit:

                                          C'est parce qu'ils sont supprimés automatiquement ?

                                          Combien de fois j'ai écris "supprimés" dans ce forum :lol:...


                                          Nous? Aucune.

                                          A aucun moment un pointeur n'est supprimé. Tu n'emploies pas un terme qui aide à exprimer, et donc à bien comprendre, ce qu'il se passe. Sur "delete ptr", on demande
                                          1- la destruction de la variable stockée à l'adresse associée au nombre ptr -- la destruction d'une adresse et autres entiers ne faisant rien
                                          et 2- la restitution de la zone mémoire associée.

                                          Quand une variable est créée sur la pile, une fois que nous sommes sortis du bloc où la variable existe, elle est automatiquement détruite, et la pile est dépilée de ce qu'il faut pour récupérer le nombre de bytes qui étaient nécessaires au stockage de la partie en surface de la variable.

                                          Et c'est là que les pointeurs intelligents tels que les unique_ptr entrent en jeu. Quand l'objet pointeur intelligent est détruit, il entraîne automatiquement la destruction de la variable référencée par le pointeur -- chose que l'on n'a pas avec une simple adresse.

                                          Résultat les unique_ptr sont parfaits pour aller dans la pile (et pas que!) : à la fin de leur portée, ces pointeurs intelligents sont détruits, ce qui déclenche la destruction de la variable pointée par l'adresse, tandis que la destruction de l'adresse (en surface) ne fait toujours rien, mais les 8octets (sur les archis 64bits contemporaines) employés pour représenter l'adresse sont restitués sur la pile.

                                          -------------

                                          EDIT

                                          > A ce moment là, il devient essentiel de donner la seule valeur connue pour représenter un pointeur invalide à notre donnée ptr:

                                          Il y a moult scénarios avec deux cas bien disjoints:

                                          - le pointeur nul sert de sentinelle et en fait, on pourrait bien utiliser n'importe quelle autre valeur (un bit_cast<T*>(1ULL) pourrait quasi tout autant faire l'affaire). Très utile dans les structures chaînées.

                                          - la programmation défensive: on ne sait pas trop ce que l'on va faire après, et pour éviter les dangling pointers, ou pire les choses qui ne riment à rien (comme détruire une variable locale via son adresse, et puis non (cf le message n°1 du fil de discussion)) on passe à nullptr. J'ai l'intime conviction de beaucoup de tels scénarios pourraient être réécrits pour ne plus avoir besoin de cette remise à zéro, qu'in fine me laisse un arrière goût de maladresse (pour des histoires d'invariants car la variable existe toujours alors que l'on a perdu l'invariant "ptr pointe vers un truc valide dont on est propriétaire")

                                          D'où mes réponses précédentes qui se résument à "delete ptr; ptr=nullptr; => code/design smell". Tout comme "move(monunique_ptr); do_something_even_a_test_with(ptr);"

                                          ---------------

                                          EDIT2:

                                          > Se baser sur les exceptions pour expliquer le problème de la gestion manuelle de la mémoire en C++ est pas trop mal en soi. Il est simple et pédagogique. Mais cela laisse penser que le problème des pointeurs et de la gestion manuelle de la mémoire est spécifique au C++, alors que c'est un problème aussi en C.

                                          C'est pour cela que ma présentation du RAII est toujours accompagnée de l'heuristique de reconnaissance rapide de mauvais code C formulée par Raymond Chen et que je résume à : "si un code (dans un monde sans exceptions) n'a pas un if toutes les deux lignes, il est très probablement incorrect". (Je retombe sur mon premier billet de blog qui fait référence à l'article d'Aaron Lahman traduit sur dvpz, et que j'avais repris en vitesse dans le contexte du SESE dans mon talk sur 2 règles qualités inadaptées au C++.) => on ne veut pas maintenir un code où l'on surveille explicitement tous les chemins d'exécution, on veut à place un code simple (et correct) où chaque ressource est surveillée par un objet dédié. D'autant que l'on ne peut pas surveiller tous les chemins à la main dans un monde à exceptions, etc, etc.

                                          Dans les autres billets & articles récents de présentation du RAII, je n'ai pas vu d'autres personnes que moi  revenir à présenter les 2 codes corrects avec et sans RAII (sachant que cela sans implique plus ou moins l'emploi de code de retours): https://alexandre-laurent.developpez.com/cpp/retour-fonctions-ou-exceptions/#LVI

                                          -
                                          Edité par lmghs 5 avril 2021 à 3:08:32

                                          • Partager sur Facebook
                                          • Partager sur Twitter
                                          C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
                                            5 avril 2021 à 16:01:40

                                            lmghs a écrit:

                                            EDIT2:

                                            > Se baser sur les exceptions pour expliquer le problème de la gestion manuelle de la mémoire en C++ est pas trop mal en soi. Il est simple et pédagogique. Mais cela laisse penser que le problème des pointeurs et de la gestion manuelle de la mémoire est spécifique au C++, alors que c'est un problème aussi en C.

                                            C'est pour cela que ma présentation du RAII est toujours accompagnée de l'heuristique de reconnaissance rapide de mauvais code C formulée par Raymond Chen et que je résume à : "si un code (dans un monde sans exceptions) n'a pas un if toutes les deux lignes, il est très probablement incorrect". (Je retombe sur mon premier billet de blog qui fait référence à l'article d'Aaron Lahman traduit sur dvpz, et que j'avais repris en vitesse dans le contexte du SESE dans mon talk sur 2 règles qualités inadaptées au C++.) => on ne veut pas maintenir un code où l'on surveille explicitement tous les chemins d'exécution, on veut à place un code simple (et correct) où chaque ressource est surveillée par un objet dédié. D'autant que l'on ne peut pas surveiller tous les chemins à la main dans un monde à exceptions, etc, etc.

                                            Dans les autres billets & articles récents de présentation du RAII, je n'ai pas vu d'autres personnes que moi  revenir à présenter les 2 codes corrects avec et sans RAII (sachant que cela sans implique plus ou moins l'emploi de code de retours): https://alexandre-laurent.developpez.com/cpp/retour-fonctions-ou-exceptions/#LVI

                                            Avec ton message, je pense comprendre pourquoi l'exemple des exceptions, même si est bien pédagogiquement, n'est pas l'approche que je prefere.

                                            lmghs a écrit:

                                            où chaque ressource est surveillée par un objet dédié

                                            Là, on est sur la problématique de la libération de la mémoire.

                                            koala01 a écrit:

                                            "aurai-je encore accès au nom de mon pointeur après avoir appelé delete ?"

                                            Là, on est sur la problématique d'un pointeur utilisé après qu'une ressource a été libérée.

                                            gbdivers a écrit:

                                            (buffer overflow par exemple)

                                            Là, on est sur un problème d'accès en dehors des limites d'un tableau.

                                            gbdivers a écrit:

                                            Un delete pour un new. Où est le new dans ton code ?

                                            Là, c'est un problème de tentative de libération de mémoire non dynamique.

                                            Et on pourrait trouver d'autres problèmes posés par la gestion de la mémoire dynamique et les pointeurs.

                                            Parce qu'au final, c'est bien ça la difficulté : on ne doit pas gérer (et expliquer) un problème et des solutions pour gérer ce problème, mais plusieurs problèmes différents.

                                            L'exemple avec les exceptions me semble limité parce qu'au final, il correspond à un seul problème : la libération de la mémoire. En soi, c'est pas un mauvais exemple quand même, parce que la solution (le RAII) est une solution qui marche (plus ou moins bien) pour les autres problèmes.

                                            Mais pour autant, il me semble important que les apprenants sachent explicitement et clairement qu'il y a pleins de problèmes à résoudre quand on utilise les pointeurs. En particulier pour ceux qui ont appris les pointeurs bruts en premier, en mode bisounours.

                                            -
                                            Edité par gbdivers 5 avril 2021 à 16:03:36

                                            • Partager sur Facebook
                                            • Partager sur Twitter
                                              6 avril 2021 à 1:11:59

                                              En fait, je crois que notre ami MO9845 ne comprend déjà pas bien ce qu'est un pointeur à la base.

                                              Alors, pour faire simple, disons qu'un pointeur est juste une valeur numérique entière (généralement) non signée qui va représenter une adresse mémoire.

                                              Autrement dit, il s'agit d'une donnée qui nous permettra de dire "telle information se trouve à tel endroit dans le système".

                                              Il faut donc bien prendre conscience que, lorsque l'on "joue" avec les pointeur, on va avoir deux informations bien distinctes: le pointeur -- déclaré par le développeur -- d'une part et l'adresse mémoire à laquelle se trouve "une ressource" bien précise.

                                              Ainsi, lorsque l'on déclare un pointeur sous la forme de Type * ptr;, on dit simplement au compilateur qu'il doit créer une donnée que nous connaitrons sous le nom de ptr et qui contient l'adresse mémoire à laquelle nous devrions (espérons) normalement trouver une ressource de type Type.

                                              Maintenant, il se peut aussi que ptr représente l'adresse mémoire à laquelle nous trouverons (ou du moins, espérons trouver) le premier élément de type type d'un ensemble contenant "un certain nombre d'éléments de types Type (autrement dit, qu'il représente l'adresse à laquelle nous trouverons le premier éléments d'un tableau de type Type). Voire même que ptr (qui serait sans doute mieux nommé autrement) représente l'adresse mémoire à laquelle se trouve l'un des éléments bien particuliers de ce tableau (ou même la première adresse mémoire qui ne fasse plus partie du tableau).

                                              Apres, ben, si on veut pouvoir effectivement accéder à une adresse mémoire bien particulière (représentée dans notre code par la donnée ptr), hé bien, il faut pouvoir faire correspondre la valeur effectivement représentée par notre donnée (ptr, toujours) à l'adresse mémoire en question.

                                              Nous avons donc deux solutions:

                                              Soit nous prenons l'adresse d'une variable qui existe déjà "sur la pile" avec un code qui ressemblerait à quelque chose comme

                                              void foo(){
                                                  int i = 15; // on crée une variable nommée i qui est
                                                              // qui est de type int
                                                  int * ptr = & i; // on utilise l'adresse de i pour
                                                                   // définir la valeur de ptr
                                                  *ptr *=2; // on multiplie "ce qui se trouve à l'adresse
                                                            // représentée par ptr" (autrement dit, la valeur 
                                                            // de i) par 2
                                                  std::cout<<i<<"\n"; // affichera 30
                                              } //les variables créées sur la pile sont détruites 
                                                // ("oubliées" par le système)
                                                // dans l'ordre inverse de leur déclaration une fois
                                                // que l'on sort de la portée dans laquelle elles ont été
                                                // déclarées
                                                // si bien que ptr va être "oubliée" en premier 
                                                // et i sera "oubliée" après

                                              Ou bien, nous avons avons recours à l'allocation dynamique de la mémoire et nous demandons l'allocation "d'une certaine quantité" de mémoire "sur le tas" pour pouvoir définir l'adresse mémoire représentée par notre pointeur.

                                              A ce moment là, nous devrons veiller à libérer la mémoire qui a été allouée au plus tard juste avant de perdre l'accès au pointeur, car, si on perd le pointeur, on perd l'adresse qu'il représente, et le système ne va pas "forcément" penser à libérer la mémoire allouée pour ce pointeur à la fin du programme

                                              En fait, cela fait déjà "un certain temps" que les systèmes d'exploitations veillent à récupérer toute la mémoire allouée à un programme quand celui-ci s'arrête. Seulement, cela n'a pas toujours été le cas et, surtout, pour que le système puisse récupérer la mémoire allouée à un programme, il faut -- d'abord et avant tout -- que le programme s'arrête.  Si le programme est destiné à  fonctionner 7jours / 7, 24 heures /24, hé bien, le système ne pourra pas décider de lui-même de récupérer la mémoire allouée au programme (car il ne pourra tout simplement pas déterminer si cette mémoire est encore utile ou non).

                                              Et, du coup, il semble assez facile à comprendre que, si l'on n'a pas libéré la mémoire dont l'adresse est représentée par notre pointeur au moment où l'on perd l'accès au pointeur, on "monopolise inutilement "dans notre programme de la mémoire à laquelle on ne sait plus accéder, alors qu'elle aurait pu être utile au système :p

                                              Cette approche va prendre une forme proche de

                                              void foo(){
                                                  /* je vais exceptionnellement séparer les différentes étapes 
                                                   * pour que ce soit plus facile à comprendre
                                                   *
                                                   */
                                                  Type * ptr; // je déclare une donnée de type "pointeur sur Type"
                                                              // qui sera appelée ptr
                                                  ptr = new Type; // je demande au système d'allouer
                                                                  // suffisamment de mémoire que pour représenter
                                                                  // une donnée de type Type, et je définis
                                                                  // la valeur de ptr avec l'adresse
                                                                  // a laquelle se trouve cette mémoire allouée
                                                  /* nota: en temps normal ces deux instructions auraient été
                                                   * auraient été regroupées sous la forme de 
                                                   Type * ptr = new Type;
                                                   */
                                                  /* je manipule "ce qui se trouve à l'adresse
                                                   * indiquée par ptr
                                                   */
                                                 delete ptr; // quand je n'ai plus besoin de la ressource
                                                             // dont l'adresse est représentée par ptr,
                                                             // je DOIS demander à ce que la mémoire
                                                             // allouée soit libérée
                                              } // ptr est "oublié" par le système ici,et je ne sais donc 
                                                // plus accéder à l'adresse mémoire qu'il représentait

                                              Et, bien sur, ces explications se limitent au cas les plus simples,car on pourrait encore complexifier les choses en voulant creer un tableau qui contienne "un certain nombre" de pointeur ( et donc, courir le risque qu'un ou plusieurs pointeur représente une adresse invalide, qui devra pouvoir être reconnue comme telle), voir même avoir recours à l'allocation dynamique de la mémoire pour la création d'un tableau parce que l'on ne saurait pas, au moment d'écrire le code, combien de pointeur notre tableau devrait pouvoir contenir.

                                              Enfin, bref...

                                              Tu l'auras sans doute compris: la notion de pointeur est une notion particulièrement complexe en C et en C++ car il s'agit d'une source de problèmes "à tiroir", dans le sens où l'on part d'une notion "tout ce qu'il y a de plus simple" (un pointeur est une donnée qui représente l'adresse mémoire à laquelle on trouvera une ressource du type indiqué) qui nous mène à prendre en considération des notions qui vont beaucoup plus loin que le cadre même de notre programme :p

                                              C'est la raison pour laquelle le meilleur conseil que l'on puisse donner à propos des pointeurs en C++ est de ne pas les utiliser si ce n'est pas absolument indispensable et de préférer les outils qui te permettent de gérer la mémoire allouée pour les pointeur de manière automatique (tels que std::unique_ptr ou le couple std::shared_ptr + std::weak_ptr).

                                              • Partager sur Facebook
                                              • Partager sur Twitter
                                              Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
                                              Anonyme
                                                6 avril 2021 à 7:56:17

                                                Merci beaucoup, tout le monde :D !! Votre aide m'a été précieuse ! Je comprends bien mieux l'utilité et le fonctionnement d'un pointeur surtout grâce à 🐨 01 😁. Mais un jour, j'ai utilisé un pointeur (parce que la valeur pouvait être nulle) et en enlevant "ptr = nullptr" avant "delete ptr", le programme plante. Pour préciser, la valeur de ce pointeur était celle d'un autre pointeur qui pouvait être nulle.
                                                • Partager sur Facebook
                                                • Partager sur Twitter
                                                  9 avril 2021 à 1:28:39

                                                  >> Mais un jour, j'ai utilisé un pointeur (parce que la valeur pouvait être nulle)

                                                  Heu, si tu as besoin d'une valeur pouvant être nulle, std::optional fera le job.

                                                  • Partager sur Facebook
                                                  • Partager sur Twitter
                                                    9 avril 2021 à 8:21:15

                                                    MO9845 a écrit:

                                                    Merci beaucoup, tout le monde :D !! Votre aide m'a été précieuse ! Je comprends bien mieux l'utilité et le fonctionnement d'un pointeur surtout grâce à 🐨 01 😁. Mais un jour, j'ai utilisé un pointeur (parce que la valeur pouvait être nulle) et en enlevant "ptr = nullptr" avant "delete ptr", le programme plante. Pour préciser, la valeur de ce pointeur était celle d'un autre pointeur qui pouvait être nulle.


                                                    Ben, ca, c'est logique...

                                                    Tu te retrouve donc à manipuler deux données, dont une représente l'adresse mémoire "source" (l'adresse mémoire qui doit effectivement être utilisée) et l'autre représente la "copie".

                                                    Tu te retrouve donc avec un schéma qui pourrait prendre la forme de

                                                                      (1)
                                                                    ------->
                                                        source                    copie
                                                                       (2)
                                                            \       <-------      /
                                                             \                   /
                                                        (3)   \                 / (4)
                                                               \               /
                                                                \             /
                                                                 \/         \/
                                                             espace mémoire représenté
                                                                       |
                                                                       | (5)
                                                                       |
                                                                      \ /
                                                                    donnée effective

                                                    Le (1) représente le fait que, pour pouvoir utiliser l'espace représenté par la source au niveau de la copie, il faut transmettre l'adresse mémoire de la donnée effective (généralement, "copie" sera un paramètre fourni à une fonction quelquonque)

                                                    le (2) représente le fait que, si tu décide de changer l'adresse mémoire au travers de la copie (en appelant delete ou new), il faudra mettre la source à jour, pour qu'elle puisse représenter l'adresse mémoire que copie a déterminé

                                                    Le (3) et le (4) nous montrent que, pour que "source" et "copie" puisse travailler sur le même espace mémoire, il doivent présenter la même valeur, vu qu'ils représentent tous les deux l'adresse mémoire à laquelle "un espace mémoire" est sensé commencer

                                                    le (5) représente le fait qu'un espace mémoire est sensé représenter une donnée, mais qu'il ne peut le faire que s'il y a "assez d'espace réservé" que pour représenter l'ensemble de la donnée et faire en sorte que, si "autre chose" doive être représenté, ce soit soit "juste avant" l'espace mémoire en question ("l'autre" donnée devant alors s'arrêter juste avant) soit "juste après ("l'autre donnée commençant tout juste après) l'espace mémoire en question.

                                                    Si une seule des flêche vient à foirer, ce sera l'ensemble du circuit qui finira par foirer.

                                                    De plus, je suis persuadé que ce n'est pas le delete qui a posé problème dans le cas que tu expose, à moins bien sur que tu n'aies appelé delete sur un des pointeurs alors que l'espace mémoire alloué à la donnée avait déjà été libéré "ailleurs" en appelant delete pour le pointeur qui la représentait (sans faire en sorte que "source" et "copie" soient bien mis à nul), ou pire, que l'espace mémoire n'avait pas été alloué par new (c'est le fameux problème de double libération de la mémoire dont parlait luc plus haut).

                                                    Car delete sur la valeur null (nullptr) -- qui es quand même la seule valeur connue comme étant invalide -- est garanti n'avoir absolument aucun effet.

                                                    Enfin, si tu te retrouve au final avec un code proche de

                                                    void foo(Type * copie){
                                                        /* ... */
                                                        copie = nullptr;
                                                        delete copie;
                                                    }

                                                    tu dois bien avoir conscience que les deux dernières lignes ne servent strictement à rien: tu demande la libération de la mémoire qui se trouve à nullptr (qui est la seule adresse mémoire invalide), si bien que l'adresse qui était à l'origine représentée par copie (et qui correspond à celle de la source) n'a en fait pas été libérée.

                                                    Si bien que, en fonction des situations, au niveau de la source, soit l'espace mémoire est encore utilisable et utilisée (si la valeur de la source n'a pas été mise à nullptr), ce qui n'est pas normal (car cet espace est sensé avoir été libéré), soit la valeur de la source a été mise à nullptr et on a purement et simplement une fuite mémoire ;)

                                                    • Partager sur Facebook
                                                    • Partager sur Twitter
                                                    Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
                                                    Anonyme
                                                      9 avril 2021 à 11:49:27

                                                      Deedolith a écrit:

                                                      Heu, si tu as besoin d'une valeur pouvant être nulle, std::optional fera le job.

                                                      Donc je me suis cassé la tête à utiliser les pointeurs...



                                                      -
                                                      Edité par Anonyme 9 avril 2021 à 11:50:25

                                                      • Partager sur Facebook
                                                      • Partager sur Twitter
                                                        9 avril 2021 à 12:07:25

                                                        Je pense pouvoir appeler ça un problème de conception : tu as un but précis (valeur != 0), tu testes une façon d'y arriver (pointeurs), ça ne fonctionne pas tu dis "j'ai un souci de pointeurs" en ne précisant pas ton but principal (et c'est normal, quand on n'a pas assez d'expérience de ne pas y penser).
                                                        • Partager sur Facebook
                                                        • Partager sur Twitter
                                                          9 avril 2021 à 14:51:51

                                                          MO9845 a écrit:

                                                          Deedolith a écrit:

                                                          Heu, si tu as besoin d'une valeur pouvant être nulle, std::optional fera le job.

                                                          Donc je me suis cassé la tête à utiliser les pointeurs...

                                                          Je pense que tu ne connais pas suffisamment les ressources disponibles sur le net.
                                                          La STL fournit de nombreuses classes apportant des solutions simple, je t'invite à les consulter. 2 sites que je trouve excellent:
                                                          cppreference.com
                                                          cplusplus.com - The C++ Resources Network


                                                          • Partager sur Facebook
                                                          • Partager sur Twitter
                                                            9 avril 2021 à 15:04:43

                                                            Après c'est normal, ça vient avec l'expérience.

                                                            C++ fournit beaucoup d'outils, il est pratiquement impossible de tous les connaître et encore moins les maîtriser.

                                                            Il n'y a qu'à regarder la liste des conteneurs, perso je n'ai jamais eu besoin de plus que array, vector et de temps à autres map (après je ne fais pas de projets pros, donc ça compte pas mais c'est pour l'exemple).

                                                            C'est comme tout, du moment qu'on n'a pas n besoin particulier, on ne connaît pas sa solution.

                                                            • Partager sur Facebook
                                                            • Partager sur Twitter
                                                            Anonyme
                                                              13 avril 2021 à 10:26:12

                                                              Une dernière question et je pense que je pourrai cliquer sur "Sujet résolu" :

                                                              A mon avis, std::unique_ptr sont éliminés de la RAM à la sortie de la boucle dans laquelle ils se trouvent. C'est juste ?

                                                              • Partager sur Facebook
                                                              • Partager sur Twitter

                                                              Supression des pointeurs obligatoire

                                                              × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                                              × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                                                              • Editeur
                                                              • Markdown