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

Langage C++ Discussion :

Soucis de pointeurs


Sujet :

Langage C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Candidat au Club
    Profil pro
    Inscrit en
    Février 2013
    Messages
    2
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2013
    Messages : 2
    Par défaut Soucis de pointeurs
    Bonjour,

    Je rencontre un souci avec le code suivant. Lorsque j'appelle une fonction qui utilise le membre Square ***matrice de l'extérieur de la classe (par exemple getGrille()->testGrid(); aucun souci). En revanche, appelé de l'intérieur de cette même classe Grid, cela produit une erreur de segmentation.

    De plus, j'observe que l'adresse renvoyée par le pointeur sur matrice n'est pas la même si je l'observe de l'intérieur de la classe ou bien par un appel extérieur.

    Je ne vois pas trop d'où vient le souci, mais j'imagine que j'ai dû faire une ânerie quelque part...

    fichier Grid.h :

    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
     
     
    #ifndef DEF_GRID
    #define DEF_GRID
     
    #include "square.h"
     
    class Grid{
     
    public:
        Grid(int abscisse, int ordonnee);
        ~Grid();
     
        void testGrid();
     
    private:
        Square ***matrice;
        int width;
        int height;
    };
     
    #endif // DEF_GRID
    fichier Grid.cpp

    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
     
     
    #include "../headers/grid.h"
     
    #include <iostream>
     
    Grid::Grid(int absc, int ord) : width(absc), height(ord){
     
        matrice = new Square** [height];
        for (int h = 0 ; h<height ; h++){
            matrice[h] = new Square* [width];
            for (int w = 0 ; w < width ; w++){
                matrice[h][w] = new Square(w,h,0);
               // std::cout << " H : "<<grille[h][w]->getH() << " W : "<< grille[h][w]->getW() << " S : " <<grille[h][w]->getState() << std::endl;
            } // CREATION OK
        }
    }
     
    Grid::~Grid(){
     
        for (int h = 0 ; h<height ; h++){
     
            for (int w = 0 ; w < width ; w++){
                delete[] matrice[h][w];
            }
            delete[] matrice[h];
        }
        delete[] matrice;
    }
     
    void Grid::testGrid(){
        //int test = matrice[0][0]->getH();
        Square* test = matrice[0][0];
        std::cout<<"TEST : " << test << std::endl;
    }
    Merci d'avance

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 635
    Par défaut
    Salut,

    Bon, déjà, ce qui serait pas mal, c'est de nous donner le code dans lequel tu fais ton getGrille()->testGrid() (la fonction en elle meme où cette ligne apparait, mais aussi la définition de la classe dont la fonction en question est membre, et éventuellement la définition de ce qui est utilisé comme valeur de retour pour getGrille() ).

    Sans cela, nous ne pourront nous baser que sur des supputations, et nous avons de fortes chances d'être tout à fait à coté de la plaque

    Ceci étant dit:
    1. Pourquoi vouloir absolument travailler avec des pointeurs sur Square et non, beaucoup plus simplement avec des objets de type Square
    2. Pourquoi vouloir gérer des pointeurs et des allocations dynamique de la mémoire alors que C++ fournit un ensemble de collections particulièrement adaptées (std::vector, par exemple)
    3. Au vu des noms, tu sembles travailler avec une matrice pleine (comprend: il existe un élément pour chaque ligne à chaque colonne). Tu aurais donc beaucoup plus facile à travailler avec un tableau d'une seule dimension composé de <nombre de ligne>* <nombre de colonnes> éléments
    De manière générale, il faut savoir qu'il est toujours recommandé d'éviter le recours aux pointeurs et à l'allocation dynamique s'ils ne sont pas absolument nécessaires.

    Dans le cas présent, il ne me semble absolument pas qu'il soit indispensable d'utiliser des pointeurs sur des Square, car, a priori, tu ne veux pas pouvoir profiter du polymorphisme (autrement, c'est une matrice de Form* que tu aurais utilisée )

    Pour la même raison, les collections fournies par la S(T)L ont l'énorme avantage de rendre la gestion dynamique de la mémoire totalement "transparente" pour leur utilisateur, ce qui a l'énorme avantage de lui permettre de ne pas devoir s'occuper de cet aspect plein d'écueil.

    Enfin, il y a plusieurs avantages à travailler avec un tableau à une seule dimension plutôt qu'avec une matrice à deux dimensions:

    D'abord, on ne s'en rend compte qu'assez rarement, lorsque le système est définitivement surchargé, mais une allocation dynamique peut parfaitement échouer lorsqu'il n'y a plus assez de mémoire disponible.

    Dans ce cas, l'opérateur new lance une exception de type std::bad_alloc. Le fait d'avoir deux appels à l'opérateur new (un pour la première dimension et l'autre en boucle pour la deuxième) complique énormément la gestion correcte de ce qui devrait se passer si une telle exception est lancée (on parle d' exception safty en anglais

    Ensuite, il faut savoir que l'on observe assez régulièrement un problème qui peut très largement "plomber" les performance en travaillant avec une matrice deux dimensions.

    En effet, lorsque l'on invoque l'opérateur new[] sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    UnType * tab = new[nombre_d_elements];
    nous aurons la certitude que tab sera composé de nombre_d_elements éléments contigus en mémoire, mais, en travaillant avec une matrice deux dimensions, sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    UnType ** matrix = new UnType*[lines]; //(1)
    for(int i = 0; i<lignes; ++i){
        matrix[i]= new UnType[columns]; //(2)
    }
    nous avons la certitudes que matrix représente un espace suffisant pour représenter lines... pointeurs contigus vers UnType.

    Par contre, il n'est absolument pas sur du tout(et l'on peut même faire le pari inverse sans avoir trop peur de perdre ) que l'adresse mémoire mémoire allouée au premier élément de matrix[i+1] sera contigüe à l'adresse du dernier élément de matrix[i].

    Peut être te demandes tu en quoi cela peut bien poser un problème la réponse tient dans la manière dont le processeur accède à la mémoire:

    Toute la mémoire qui est disponible sur ton ordinateur est découpée en "pages" d'une certaine taille que l'on pourrait comparer aux pages d'un dictionnaire en papier.

    Chaque fois que l'on demande au processeur d'accéder à une adresse particulière, il va commencer par chercher la page sur laquelle se trouve cette adresse, un peu à la manière de ce que tu ferais en cherchant un mot particulier dans ton dictionnaire.

    A partir de là, il aura "relativement facile" à trouver l'adresse recherchée.

    Mais si l'on demande, juste après, d'accéder à une adresse qui ne se trouve pas sur la page à laquelle il se trouve, il devra... de nouveau chercher la page sur laquelle se trouve la nouvelle adresse demandée, exactement comme ce que tu devrais faire avec ton dictionnaire si je te demandais la définition de "zébu" après t'avoir demandé celle de "cacahuète".

    Le fait de rechercher la "page qui convient" prend toujours du temps. En informatique, on parle volontiers de "cache miss", ce qui correspond au fait que la donnée à laquelle on veut accéder ne se trouve pas sur la même page que la dernière donnée à laquelle on a accédé.

    En considérant qu'une matrice de L lignes et de C colonnes correspond en réalité à un tableau composé de L*C éléments et en demandant directement l'allocation dynamique en une seule fois pour ces L*C éléments, nous aurons la certitudes que ces L*C éléments seront contigus en mémoire.

    Alors, bien sur, il n'est pas impossible que ces L*C se retrouvent sur plusieurs pages, tout comme il n'est pas impossible que tu aies plusieurs pages de mots commençant par CAC dans ton dictionnaire.

    Mais ce seront chaque fois des pages contigües, au lieu d'être des pages "disséminées selon bon vouloir du système d'exploitation".

    il y a donc de fortes chances pour que le temps nécessaire pour trouver la page sur laquelle se trouve la donnée recherchée soit fortement raccourci, "simplement" parce qu'il y a "moins de pages à tourner".

    Je ne dis absolument pas que tu gagneras beaucoup en performances, ni même que tu gagneras d'office en performances car cela dépend d'énormément de choses, comme la manière dont tu parcoures les éléments, le nombre d'éléments à parcourir et la charge globale du système, mais une chose est sure: en plus d'être au final plus facile à utiliser, tu auras obtiendras au minimum des performances égales à supérieures à celles que tu aurais obtenue en travaillant avec une représentation réelle (deux dimensions ou plus) d'une matrice.

    Enfin, le fait de travailler de la sorte va t'inciter à créer une fonction qui utilise une formule finalement assez simple pour accéder à un élément donné.

    Cette fonction prendra, tu t'en doutes surement, un numéro de ligne et un numéro de colonne comme paramètre.

    On a généralement l'habitude de considérer que la première valeur est le numéro de ligne et la deuxième le numéro de colonne, et de considérer que l'élément de la ligne 1 se trouvant à la colonne 5 est contigu à l'élément de la ligne 1 se trouvant à la colonne 6

    Mais "il se peut" que, pour une raison qui te serait propre, tu veuilles travailler en considérant que l'élément de la ligne 1 se trouvant à la colonne 5 est contigu à l'élément de la ligne 2 se trouvant à la colonne 5 (en gros, d'inverser la représentation physique de ta matrice).

    Le fait de passer par une fonction qui te permette d'accéder à un élément donné te permet de faire ce genre de changement de manière totalement transparente pour l'utilisateur: il continuera à introduire le numéro de la ligne en premier paramètre et le numéro de la colonne en deuxième paramètre, sans être avoir à s'inquiéter de savoir dans quel ordre ces deux paramètres seront utilisés.

    au final, ta classe Grid pourrait très bien ressembler à 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
    class Grid{
        public:
            Grid(int width, int height):width_(width),height_(height),data_(width*height){}
             /* le "coup de génie" : pour accéder à un élément donné, utilise
              * la formule position = ligne * largeur + colonne
              */
             Square & itemAt(int line, int column){return data[widht_*line+column];}
             const Square & itemAt(int line, int column)const{return data[widht_*line+column];}
        private:
            int width_;
            int height_;
            std::vector<Square> data_;
    };
    Avec une telle classe, tu n'as meme plus besoin de t'em...der à gérer toi meme la mémoire, tout est fait automatiquement

    La seule différence, c'est que, au lieu d'accéder à un élément donné sous la forme de
    tu y accédera sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    data.itemAt(ligne, colonne);
    en profitant de tous les avantages que j'ai cité plus haut
    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
    Candidat au Club
    Profil pro
    Inscrit en
    Février 2013
    Messages
    2
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2013
    Messages : 2
    Par défaut
    Premièrement, je tiens à te remercier d'avoir pris le temps de rédiger un tel pavé, c'est plutôt gentil de ta part.

    J'ai appris quelques petites choses en le lisant et eu la confirmation que rédiger vite fait ce bout de code avant d'aller dormir n'était pas la meilleure des idées.

    Pour ce qui relève du côté pratique, je sais bien qu'il ne faut pas utiliser les pointeurs à moins que ce ne soit absolument nécessaire, mais le code que j'ai fourni n'a rien d'une "production" ; il se voulait à la base juste une bricole pour voir si j'arrivai à manipuler un peu les pointeurs et le concept de VLA (qui n'est pas spécialement recommandé non plus, j'en ai conscience).

    Je donne le code de la classe appelante ici si tu veux, mais je ne suis pas convaincu que le problème vienne de là, étant donné que ça fonctionne très bien en appelant testGrid() de l'extérieur, et que le bug survient quand j'utilise la fonction de l'intérieur de la classe.

    Je manque sûrement un truc complètement obvious, mais c'est un peu comme ses propres fautes d'ortographe, on a souvent un peu de mal à les retrouver...

    Model.h :

    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
     
     
    #ifndef DEF_MODEL
    #define DEF_MODEL
     
    #include "grid.h"
     
    class Model{
     
    public:
        Model();
        void createGrid(int abscisse, int ordonnee);
        Grid* getGrille();
     
    private:
        Grid *grille;
    };
    #endif
    Model.cpp :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
     
    #include "../headers/model.h"
     
    Model::Model() : pathfindRunning(false){
    }
     
    void Model::createGrid(int absc, int ord){
        grille = new Grid(absc, ord);
    }
     
    Grid* Model::getGrille(){
        return grille;
    }
    Si dans mon main j'appelle

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
     
    modele->getGrille()->testGrid();
    Je ne rencontre aucun souci, tout fonctionne parfaitement. En revanche, si j'essaie de manipuler Square*** au sein de grid, je rencontre une Segfault, par exemple avec ceci :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
     
    int Grid::getSquareState(int ordonnee, int abscisse){
        return matrice[ordonnee][abscisse]->getState();
    }
    Je ne poste pas tant pour faire marcher ce bout de code, qui n'a en soi aucune importance et qui finira dans un coin de dossier dès qu'il sera fonctionnel, mais plus pour comprendre ce que je fais de travers et pourquoi.

  4. #4
    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
    c'est peut-être que var[][]->truc() n'est valable que si var peut subir successivement operator[](), operator[](), operator->() et .func()

    Pour ca, il faut probablement que var soit un (Square*)[][], donc un Square***.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 635
    Par défaut
    Citation Envoyé par Aethusium Voir le message
    Premièrement, je tiens à te remercier d'avoir pris le temps de rédiger un tel pavé, c'est plutôt gentil de ta part.
    De rien, ce n'est pas le pire que j'aie écrit
    J'ai appris quelques petites choses en le lisant et eu la confirmation que rédiger vite fait ce bout de code avant d'aller dormir n'était pas la meilleure des idées.
    Rarement, en effet...

    Pour ce qui relève du côté pratique, je sais bien qu'il ne faut pas utiliser les pointeurs à moins que ce ne soit absolument nécessaire, mais le code que j'ai fourni n'a rien d'une "production" ; il se voulait à la base juste une bricole pour voir si j'arrivai à manipuler un peu les pointeurs et le concept de VLA (qui n'est pas spécialement recommandé non plus, j'en ai conscience).
    En fait, "tout ce qu'il faut savoir" sur les pointeurs, c'est:
    1. que ce ne sont jamais que des valeurs entières (généralement) non signées qui contiennent l'adresse à laquelle on va trouver un objet "de type compatible" avec le type indiqué
    2. Que l'opérateur ++ signifie "prend l'adresse mémoire de l'objet suivant"
    3. le recours aux pointeurs demande énormément d'attention pour en éviter les écueils
    4. qu'il n'y a *que quelques cas* dans lesquels il soit effectivement opportun de les utiliser
    5. que l'utilisation d'un pointeur peut etre "un mal nécessaire", celle d'un pointeur de pointeur est excessivement dangereuse et celle de chaque indirection supplémentaire est une raison de plus pour en éviter l'usage

    Une fois que tu pas "percuté" sur les deux premiers points, leur utilisation devient facile (j'ai, personnellement "percuté" sur le deuxième... juste après un examen de C , ce qui ne m'a pas empêché de le réussir )

    Mais je vais peut etre m'attarder un peu sur les points 3, 4 et 5.

    Les pointeurs sont, globalement, associés à une pratique qui s'appelle "l'allocation dynamique de la mémoire".

    Elle consiste, pour faire simple, à demander au système d'exploitation de réserver un "espace suffisant" en mémoire pour pouvoir y placer l'ensemble des données nécessaires à la représentation d'un objet d'un type donné, voire, de plusieurs objet contigus en mémoires d'un type donné.

    Quand on l'utilise, on dit aussi explicitement au système d'exploitation que nous déciderons nous même "du moment où l'on n'en aura plus besoin", et donc du moment "le plus opportun" où le système pourra considérer que l'espace mémoire est à nouveau disponible.

    On prend, littéralement, la responsabilité de la durée de vie de l'objet (ou des objets) qui se trouve(nt) dans l'espace mémoire en question, et c'est là que les problèmes commencent.

    En effet, il faut s'astreindre à une discipline particulièrement stricte pour éviter:
    1. de "perdre" une adresse où se trouve un objet alloué de manière dynamique avant de l'avoir détruit (autrement, on a ce qu'on appelle une "fuite mémoire" qui va, tôt ou tard, menacer la stabilité de tout le système)
    2. de se retrouver avec un pointeur dont l'adresse correspond à un élément qui a déjà été détruit (dont on a dit au système qu'il pouvait considérer l'espace mémoire disponible)

    Ta classe model, par exemple
    Model.h :

    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
     
     
    #ifndef DEF_MODEL
    #define DEF_MODEL
     
    #include "grid.h"
     
    class Model{
     
    public:
        Model();
        void createGrid(int abscisse, int ordonnee);
        Grid* getGrille();
     
    private:
        Grid *grille;
    };
    #endif
    Model.cpp :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
     
    #include "../headers/model.h"
     
    Model::Model() : pathfindRunning(false){
    }
     
    void Model::createGrid(int absc, int ord){
        grille = new Grid(absc, ord);
    }
     
    Grid* Model::getGrille(){
        return grille;
    }
    , ne va pas sans poser de problèmes de fuites mémoire et ce, en deux occasions:
    Imagines, par exemple, que j'écrive un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    int main(){
        Model m;
        m.createGrid(5,10);
        m.createGrid(10,5); // (1)
        return 0;
    } //(2)
    tu te retrouveras avec deux fuites mémoires (sur un code de 6 lignes, c'est un ratio sympa, hein :question):
    La première aura lieu lors du deuxième appel à createGrid (1), parce que l'adresse mémoire représentée par grille et qui correspond (avant le deuxième appel à createGrid()) à l'adresse à laquelle se trouve la première grille sera remplacée par l'adresse à laquelle se trouve la deuxième, sans que la première n'ait été détruite.

    La deuxième aura lieu au moment où tu quittera la fonction, au niveau de l'accolade fermante "}" (2) parce que m sera automatiquement détruit et que le destructeur de la classe Model ne libère pas l'espace mémoire correspondant à son membre grille.

    Pour mon exemple de code, ce n'est pas *excessivement* grave dans le sens où l'application est terminée juste après la deuxième fuite mémoire, et où l'on est en droit d'espérer que le système considérera que toute la mémoire qui était utilisée par l'application est à nouveau disponible.

    Cependant, si on est en droit de l'espérer, ce ne fut pas forcément toujours le cas (certaines très vieilles versions de windows "oubliaient" ce détail, il me semble )

    Je vais te laisser réfléchir à la manière d'éviter ces deux fuites mémoires (attention, il y a un piège )

    Pour te parler du deuxième problème qu'il faut arriver à éviter (celui de se retrouver avec un pointeur correspondant à un espace mémoire déjà libéré), je vais (bien que je n'aime pas du tout cette pratique ) modifier "un tout petit peu" ta classe Model, en lui rajoutant une mutateur sur la grille:

    Elle ressemblerait donc à quelque chose comme

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Model{
     
    public:
        Model();
        void createGrid(int abscisse, int ordonnee);
        Grid* getGrille();
        void setGrille(Grid* newGrid){grille = newGrille;}
    private:
        Grid *grille;
    };
    Ce mutateur présente aussi un problème de fuite mémoire potentielle, mais un mutateur n'est pas sensé faire autre chose que d'assigner la valeur indiquée au membre en question en question

    Par contre, il nous permettrait d'écrire un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    int main(){
        Grid * g = new Grid(5,10);
        Model m;
        m.setGrille(g);
        /* ... */
        delete g; /* CRACK  (1)*/
        /* ... */
        m.getGrid()->uneFonctionQuelconque() /* BOUM */
    }
    Ce code est, je te l'accorde, totalement absurde.

    Il a, cependant l'énorme avantage de mettre en évidence une situation qui peut parfaitement se présenter de manière "éparpillée" entre différentes fonctions et sur laquelle il est alors particulièrement difficile de mettre la main.

    Le problème vient du fait que le membre "grille" de la classe Model représente la même adresse mémoire que g une fois que l'on a passé la ligne 4.

    "Tout va très bien (madame la marquise)" jusqu'à la ligne 6, parce que l'on dit explicitement au système "voilà, je n'ai plus besoin de l'espace mémoire qui commence à l'adresse représentée par g".

    A partir de cette ligne, le système est donc tout à fait en droit d'utiliser cet espace mémoire "comme bon lui semble".

    Sauf que le membre "grille" de m correspond toujours à cet espace mémoire

    Une fois que l'on a passé la ligne 6, toute tentative d'accès (de manière directe ou indirecte) à l'espace mémoire représenté par le membre grille de m ne peut plus résulter qu'en un undefined behaviour (un comportement indéfini).

    Si ce n'est qu'une tentative d'accès en lecture, tu risque d'obtenir des données totalement aberrante, si c'est une tentative de modification tu te retrouveras, dans le meilleur des cas, avec une erreur de segmentation

    Pour en conclure avec le point 3, je vais juste te rappeler que chaque classe ayant sémantique de valeur respecte ce que l'on appelle la forme canonique orthodoxe de coplien. Le "piège" que je te tend en te laissant réfléchir à la manière de corriger le problème des fuites mémoires est fortement lié à cette règle .

    Intéressons nous donc maintenant aux cas dans lesquels il est opportun d'utiliser des pointeurs.

    Le premier cas survient lorsqu'une relation "cyclique" pourrait empêcher le compilateur de déterminer la taille de l'espace mémoire nécessaire pour représenter un objet:

    Mettons que nous ayons un type A et un type B avec comme contrainte que le type A doit pouvoir accéder à un objet particulier de type B et que le type B doit pouvoir accéder à un objet de type A.

    C'est typiquement ce qui arrive dans une relation "parent / enfant" lorsque l'enfant doit pouvoir s'adresser à son parent

    Il est alors important de se souvenir du fait que la taille de l'espace mémoire nécessaire pour représenter un objet de n'importe quel type correspond au minimum à la somme des tailles de tous ses membres (non statiques).

    Je ne vais pas trop m'étendre sur les détails, mais, si je dis "au minimum", c'est parce qu'il y faut aussi parfois compter des "suppléments"

    Cela signifie que si le type A doit "contenir" un objet de type B, la taille de l'objet de type A correspond à celle du type B + la taille de tous les autres membres de A.

    Mais, c'est valable aussi dans l'autre sens : si B doit contenir un objet de type A, la taille de B correspond celle du type A + la taille de tous les autres membres de B.

    Dés lors, mets toi un peu à la place du "pauvre compilateur" qui devrait calculer la taille de A et de B.

    Pour calculer la taille de A, tu aurais besoin de connaitre celle de B, mais, pour calculer la taille de B, il faudrait connaitre celle de A, qui, comme on vient de le voir, nécessiterait de connaitre la taille de B

    tu rentrerais dans une boucle dont il te serait totalement impossible de sortir.

    Par chance, il arrivera un moment où le compilateur ne pourra plus continuer à boucler de la sorte, et s'arrêtera en te traitant de tous les noms d'oiseaux

    Pour éviter ce problème, il "suffirait" qu'une des inconnue (la taille de A ou celle de B) soit remplacer par "quelque chose" dont on connait parfaitement la taille et qui soit en mesure de représenter un objet du type envisagé.

    Ce "quelque chose" est tout trouvé : c'est un pointeur.

    En effet, la taille d'un pointeur est parfaitement connue, comme la taille de chaque type susceptible de représenter une valeur numérique.

    Elle correspond (au minimum) au nombre de bits "suffisant" pour permettre de représenter "n'importe quelle adresse mémoire accessible par le système"

    En remplaçant "simplement" l'objet de type A dans le type B par un pointeur sur un objet de type A, la taille du type B est maintenant parfaitement connue: elle correspond à la somme de la taille nécessaire pour représenter un pointeur en mémoire et de la taille de tous les autres membres de B (et des différents "suppléments" éventuels dont j'ai dit que je ne parlerai pas ).

    Il devient alors parfaitement possible de calculer la taille de A selon le même principe : elle correspond à la somme de la taille d'un B + la taille de tous les autres membres de A (et des différents "suppléments" éventuels dont j'ai dit que je ne parlerai pas )

    Le deuxième cas où cela peut être opportun, c'est lorsqu'il s'agit d'indiquer qu'un objet peut ne pas exister du tout.

    Attention, je ne dis pas (et c'est voulu) qu'un objet peut être invalide, je dis qu'il peut, purement et simplement ne pas exister.

    Un objet peut en effet très bien exister mais être constitué de valeurs qui ne respectent pas certaines conditions.

    L'inexistance d'un objet peut arriver, par exemple, lorsqu'un objet particulier doit être mis en relation avec un objet "parent", "enfant" ou "frère".

    Cet objet peut être la "racine" et n'avoir pas de parent, peut être une "feuille" et n'avoir pas d'enfant ou encore être "fils unique" et n'avoir pas de frère

    Les autres cas sont tous plus ou moins liés à l'héritage et au polymorphisme.

    En effet, on ne peut profiter du polymorphisme d'héritage qu'au travers de références ou de pointeurs.

    Or, il est impossible de créer une collection de références sur des objets.

    La seule solution qu'il nous reste est donc de passer par des pointeurs.

    C'est d'autant plus vrai que l'on entre alors dans une logique qui concerne les classes ayant sémantique d'entité.

    "Typiquement", ce genre de classes n'a pas vocation à être copié ni assigné, et l'on veille généralement à faire en sorte d'éviter la copie ou l'affectation (C++11 rend d'ailleurs les choses encore plus faciles avec la notion de fonction "deleted")

    Or, à moins de recourir à la sémantique de mouvement, les collections fournies par la S(T)L se basent sur le principe que l'objet qu'elles contiennent est justement copiable.

    C'est là que la notion de pointeur devient intéressante parce que, comme je te l'ai dit, un pointeur n'est jamais "qu'une valeur numérique non signée qui correspond à l'adresse mémoire à laquelle on va trouver un objet" et qu'une valeur numérique, c'est parfaitement copiable .

    Pour bien faire, il s'agira de respecter les cinq piliers de la programmation orientée objets que l'on connait sous l'acronyme de SOLID Mais ca, c'est une toute autre histoire (que je te raconterai peut etre "si tu es sage" )

    en dehors de ces différents cas de figures, le meilleur conseil que l'on puisse te donner est "évite au maximum le recours aux pointeurs, ils apportent beaucoup plus de problèmes que de solutions "

    Le cinquième point enfin devrait être beaucoup plus facile à expliquer

    Comme je n'ai pas cessé de te le rappeler tout au long de cette intervention, l'utilisation de pointeur pose déjà de nombreux soucis et nécessite une attention accrue pour arriver à éviter les différents écueils.

    Mais il arrive que l'on n'ait, tout simplement "pas le choix" et qu'il faille donc "faire avec".

    A chaque fois que tu décideras de rajouter une indirection (Square * au lieu de Square, Square ** au lieu de Square*, et ainsi de suite), tu feras augmenter le nombre d'écueils auxquels tu sera confronté de manière exponentielle, pour ne pas dire logarithmique (ou peu s'en faut).

    Or, l'une des règles que je n'hésite pas à qualifier de règle d'or de la programmation est, selon moi, que la solution la plus simple est toujours la moins compliquée (heu... pardon : la meilleure).

    Si donc tu n'a pas le choix, il faudra bien que tu aies recours aux pointeurs, ce sera sans doute inévitable.

    Mais tu auras normalement toujours une solution plus simple que de rajouter une indirection. Tu devrais toujours choisir l'autre solution
    Si dans mon main j'appelle

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
     
    modele->getGrille()->testGrid();
    heu... as tu pensé à appeler modele->createGrid (mais oui, bien sur, sinon ca ne fonctionnerait pas)
    Je ne rencontre aucun souci, tout fonctionne parfaitement. En revanche, si j'essaie de manipuler Square*** au sein de grid, je rencontre une Segfault, par exemple avec ceci :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
     
    int Grid::getSquareState(int ordonnee, int abscisse){
        return matrice[ordonnee][abscisse]->getState();
    }
    Je ne poste pas tant pour faire marcher ce bout de code, qui n'a en soi aucune importance et qui finira dans un coin de dossier dès qu'il sera fonctionnel, mais plus pour comprendre ce que je fais de travers et pourquoi.
    Es tu simplement sur que ordonnee et abscisse sont bel et bien dans la limite des valeurs admises

    Pour rappel, les indices en C et en C++ sont basé sur 0: pour un tableau de N éléments, on ne peut accéder qu'aux indice allant de 0 à N-1 inclus.

    En dehors de tout cas dans lequel on se trouve avec une adresse mémoire correspondant à un espace mémoire que l'on a "rendu au système", la cause la plus commune d'erreur de segmentation est due au fait que l'on essaye d'accéder à un élément qui n'existe pas parce que l'indice est "hors limite".

    Comme tu utilises deux indices, tu as deux fois plus de chances de donner un indice hors limites
    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

  6. #6
    Membre Expert

    Avatar de germinolegrand
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Octobre 2010
    Messages
    738
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : Tourisme - Loisirs

    Informations forums :
    Inscription : Octobre 2010
    Messages : 738
    Par défaut
    Citation Envoyé par koala01
    De rien, ce n'est pas le pire que j'aie écrit
    La preuve : tu viens de faire encore plus long

    Pour les pointeurs pour ma part le plus important pédagogiquement est d'en comprendre l'arithmétique.

    Le plus important en matière de codage : toujours pouvoir leur garantir une valeur bien définie et pointant sur une zone valide (ce qui implique une certaine discipline dans leur manipulation).

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 635
    Par défaut
    Citation Envoyé par germinolegrand Voir le message
    La preuve : tu viens de faire encore plus long
    Et pourtant ce n'est toujours pas le pire que j'ai fait

    Un jour, quand j'ai voulu poster ma réponse, le forum m'a gentilement répondu "désolé, votre message dépasse la limite des 65000 (et quelques) caractères autorisés pour ce champs"

    Pour les pointeurs pour ma part le plus important pédagogiquement est d'en comprendre l'arithmétique.
    Ce qui correspond en gros au point deux de ma prose
    Le plus important en matière de codage : toujours pouvoir leur garantir une valeur bien définie et pointant sur une zone valide (ce qui implique une certaine discipline dans leur manipulation).
    Tout à fait, mais c'est loin d'être toujours évident, et il faut en rester conscient
    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. Petit soucis de pointeur !
    Par Franck.H dans le forum C
    Réponses: 4
    Dernier message: 05/05/2013, 11h57
  2. Réponses: 5
    Dernier message: 07/04/2010, 16h02
  3. Petit soucis de pointeurs
    Par Blacky121 dans le forum Débuter
    Réponses: 2
    Dernier message: 26/05/2008, 15h11
  4. [mfc]un ptit souci de pointeur
    Par isidore dans le forum MFC
    Réponses: 3
    Dernier message: 14/05/2006, 14h11

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