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

SL & STL C++ Discussion :

[std::list] changement incompréhensible des adresses mémoires


Sujet :

SL & STL C++

  1. #1
    Membre à l'essai
    Profil pro
    Inscrit en
    Février 2004
    Messages
    15
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2004
    Messages : 15
    Points : 10
    Points
    10
    Par défaut [std::list] changement incompréhensible des adresses mémoires
    Bonjour à tous,

    débutant en C++ (mais déjà confirmé en Java, donc pas débutant en programmation), je suis confronté à un problème de mémoire que je ne comprend pas (n'ayant jamais eu à gêrer la mémoire auparavent). Le programme est un petit jeu en 2D.

    J'utilise Visual C++ .net, et mon problème se situe dans une liste (std::list). J'utilise une liste de pointeurs vers des objets (list<GameObject*>) dont j'actualise l'état et que j'affiche grâce à la méthode GameObject::refresh().

    voici le code:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    void GameState::render()
    {
        list<GameObject*>::iterator iter;
     
        for( iter = m_renderable_objects.begin(); iter != m_renderable_objects.end(); iter++) 
        {
            if(!((*iter)->getParent()))
                (*iter)->refresh(SDL_GetTicks());
        }
    }
    Parmis ces GameObject à actualiser et afficher, deux sous-classe sont incriminée : Sea et Boat.
    Boat possède une liste de GameObject enfants. On a donc (en simplifiant le vrai code) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    void Boat::refresh(int _time)
    {
        list <GameObject *>::iterator end = m_children.end();
        for (list <GameObject *>::iterator iter = m_children.begin(); iter != end; ++iter)
        {
            (*iter)->refresh(_time);
    }
    Sea, quand à elle, dessine dans son Sea::refresh() une spline, et donc possède un double *m_second_derivative qui sert à stocker un tableau de double représentant la dérivée seconde des points de contrôles de la spline (il s'agit d'une spline cubique).
    Voilà donc le code (simplifié là encore) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
     
    void Sea::refresh(int _time)
    {
        animate();
        calculateSecondDerivative();
        display();
    }
     
    void Sea::calculateSecondDerivative()
    {
        double yp0 = (m_ctrl_points[1].getY()-m_ctrl_points[0].getY()) /
                     (m_ctrl_points[1].getX()-m_ctrl_points[0].getX());
        double ypnm1 = (m_ctrl_points[m_nb_ctrl_points-1].getY()-m_ctrl_points[m_nb_ctrl_points-2].getY()) /
                       (m_ctrl_points[m_nb_ctrl_points-1].getX()-m_ctrl_points[m_nb_ctrl_points-2].getX());
        int i,k;
        double p,qn,sig,un,*u;
     
        u = new double[m_nb_ctrl_points];
     
        //evite la pente initiale trop importante
        if (yp0 > 1e30)
            m_second_derivative[0]=u[0]=0.0;
        else
        {
            m_second_derivative[0] = -0.5;
            u[0]=(3.0/(double)(m_ctrl_points[1].getX()-m_ctrl_points[0].getX()))*((double)(m_ctrl_points[1].getY()-m_ctrl_points[0].getY()) /
                 (double)(m_ctrl_points[1].getX()-m_ctrl_points[0].getX())-yp0);
        }
     
        for (i=1;i<m_nb_ctrl_points-1;i++)
        {
            sig=(double)(m_ctrl_points[i].getX()-m_ctrl_points[i-1].getX())/(double)(m_ctrl_points[i+1].getX()-m_ctrl_points[i-1].getX());
            p=sig*m_second_derivative[i-1]+2.0;
            m_second_derivative[i]=(sig-1.0)/p;
            u[i]=(double)(m_ctrl_points[i+1].getY()-m_ctrl_points[i].getY())/(float)(m_ctrl_points[i+1].getX()-m_ctrl_points[i].getX()) -
                 (double)(m_ctrl_points[i].getY()-m_ctrl_points[i-1].getY())/(double)(m_ctrl_points[i].getX()-m_ctrl_points[i-1].getX());
            u[i]=(6.0*u[i]/(double)(m_ctrl_points[i+1].getX()-m_ctrl_points[i-1].getX())-sig*u[i-1])/p;
        }
     
        //evite la pente finale trop importante
        if (ypnm1 > 0.99e30)
            qn=un=0.0;
        else {
            qn=0.5;
            un=(3.0/(double)(m_ctrl_points[m_nb_ctrl_points-1].getX()-m_ctrl_points[m_nb_ctrl_points-2].getX()))*(ypnm1-(double)(m_ctrl_points[m_nb_ctrl_points-1].getY() -
               m_ctrl_points[m_nb_ctrl_points-2].getY())/(double)(m_ctrl_points[m_nb_ctrl_points-1].getX()-m_ctrl_points[m_nb_ctrl_points-2].getX()));
        }
     
        m_second_derivative[m_nb_ctrl_points-1]=(un-qn*u[m_nb_ctrl_points-2])/(qn*m_second_derivative[m_nb_ctrl_points-2]+1.0);
        for (k=m_nb_ctrl_points-2;k>=0;k--)        m_second_derivative[k]=m_second_derivative[k]*m_second_derivative[k+1]+u[k];
     
        delete [] u;
    }

    Et si vous êtes encore là (je sais que cet exposé est un peu long, mais j'ai peur de ne pas être clair !), voilà où est le problème :

    lors de la première itération de ma boucle principale de jeu, les adresses mémoires de la liste Boat::m_children sont les suivantes (d'après le debugger) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    m_children::Myhead 0x003e2b60
    m_children::_Next 0x003e5bd8
    m_children::_Prev 0x003e5bd8
    la liste ne contient qu'un élément et j'observe bien m_children::_Mysize = 1

    eh bien juste après le passage dans Sea::calculateSecondDerivative(), qui est affiché juste après, ces valeurs changent pour des valeurs bizarres :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    m_children::Myhead 0x003e2b60
    m_children::_Next 0x503e382b
    m_children::_Prev 0xbeccc318

    evidemment, dès la deuxième itération, le programme plante avec un joli "Access violation reading location 0x503e382b", ce qui me parait logique.

    Ce qui m'est incompréhensible, c'est comment le remplissage d'un tableau de double dans une classe peut agir sur les adresses d'une liste qui est dans une autre classe...

    Peut-être est-ce le symptôme d'un problème qui vient d'ailleur, mais je ne sais pas du tout où regarder...

    Quelqu'un aurait-il une piste ?

    Antoine

  2. #2
    Expert confirmé
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Décembre 2003
    Messages
    3 549
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Décembre 2003
    Messages : 3 549
    Points : 4 625
    Points
    4 625
    Par défaut
    Tes fonctions membres sont bien virtuelles ?

  3. #3
    tut
    tut est déconnecté
    Membre averti
    Avatar de tut
    Inscrit en
    Juillet 2002
    Messages
    373
    Détails du profil
    Informations forums :
    Inscription : Juillet 2002
    Messages : 373
    Points : 394
    Points
    394
    Par défaut
    ça sent le "jardinage"...
    Vérifie que tu n'écries pas au-delà des limites de tes tableaux, parce que si c'est le cas, tu peux très bien aller écrire dans ta liste.... et alors là...

  4. #4
    Membre à l'essai
    Profil pro
    Inscrit en
    Février 2004
    Messages
    15
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2004
    Messages : 15
    Points : 10
    Points
    10
    Par défaut
    Malheureusement, il me semble bien que je ne sors pas des bornes du tableau.
    Voilà un extrait du constructeur de Sea :
    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
     
    Sea::Sea(const float _width, const float _height, const int _sampling)
    {
        m_nb_ctrl_points = (int)ceilf(m_width/145);
        m_ctrl_points = new Position[m_nb_ctrl_points];
        m_ctrl_points_ref = new Position[m_nb_ctrl_points];
     
        float step = m_width / (m_nb_ctrl_points-1);
     
        m_second_derivative = new double[m_nb_ctrl_points];
     
        for (int i = 0; i < m_nb_ctrl_points; ++ i)
        {
            m_ctrl_points[i] = Position(i * step, 0);
            m_ctrl_points_ref[i] = m_ctrl_points[i];
            m_second_derivative[i]=0;
        }
     
    }
    et voilà à quoi ressemble la boucle dans laquelle les adresses changent de manière suspecte, dans Sea::calculateSecondDerivative()
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
        for (i=1;i<m_nb_ctrl_points-1;i++)
        {
            ...
        }
    donc là, je boucle à partir du deuxième élément jusqu'à l'avant dernier. Aucune raison de sortir des bornes donc... J'ai tout de même vérifié si m_nb_ctrl_points ne change pas de valeur, et sa valeur reste toujours la même.

    Donc je reste bloqué sur cette erreur. Des idées ?

    Antoine

    EDIT: GameObject::refresh() est déclarée virtuelle, ainsi que Boat::refresh() et Sea::refresh().

  5. #5
    mat.M
    Invité(e)
    Par défaut
    for (int i = 0; i < m_nb_ctrl_points; ++ i)



    En C pour remplir un tableau de 10 éléments on procéde comme cela :
    i est d'abord initialisé à 0 puis si i=10 alors on sort de la boucle
    int entiers[10];
    int i;
    for(i=0;i<10;i++) entiers[i]=0;

    pour la boucle que tu as donnée précedemment , i est incrémenté AVANT d'être affecté donc les bornes de m_ctrl_points[i] vont être dépassées

  6. #6
    Membre confirmé
    Profil pro
    Inscrit en
    Novembre 2003
    Messages
    394
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2003
    Messages : 394
    Points : 473
    Points
    473
    Par défaut
    Non Mat.M. for(int i=0; i < 10; ++i) et for(int i=0; i < 10; i++) donne 10 itérations avec i variant de 0 à 9.
    Si tu veux t'en convaincre, écris un programme de test

  7. #7
    Membre confirmé
    Profil pro
    Inscrit en
    Juin 2005
    Messages
    731
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2005
    Messages : 731
    Points : 574
    Points
    574
    Par défaut
    Le fait d'avoir un prev et un next différents ne me choque pas trop étant donné que tu utilises un objet liste. Le Head n'a pas changé d'autant plus.
    Ca plante où exactement en debug ?
    Il n'y a pas tout le code et les méthodes de calcul sont un peu lourdes à voir d'un coup d'oeil.
    Les prototypes des classes, quels sont-ils ?

  8. #8
    Membre à l'essai
    Profil pro
    Inscrit en
    Février 2004
    Messages
    15
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2004
    Messages : 15
    Points : 10
    Points
    10
    Par défaut
    _Prev et _Next peuvent bien avoir la valeur qu'ils veulent en mémoire, je dois avouer que je m'en tamponne le coquillard tant que ça marche ! Mais là, ben ça marche pas...

    donc comme je l'ai dit, suite à l'initialisation, ma liste se place sur certaines adresses mémoires. _Myhead est l'adresse qui sert de repère pour la fin de la liste. C'est ce qui est retourné quand on appelle list<_Ty>::end(). _Next contient le premier élément de la liste, qui est retourné par list<_Ty>::begin().

    Et bien lorsque dans la méthode Sea::calculateSecondDerivative(), je rempli Sea::m_second_derivative, qui est un double * initialisé dans le constructeur de Sea comme étant un tableau à m_nb_ctrl_points éléments
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    m_second_derivative = new double[m_nb_ctrl_points];
    il se trouve que l'adresse de Boat::m_children::_Next change pour une autre valeur. Or, la liste n'étant pas changée par une méthode, et n'appartenant pas au même objet, je ne vois pas de raison à cela.

    Le résultat est que lors du prochain accès à cette liste, à n'importe quel endroit du code après l'appel à Sea::calculateSecondDerivative(), l'utilisation de l'itérateur renvoyé par Boat::m_children::begin() résulte en un joli "Access violation reading location 0x503e382b" (la valeur qu'a pris _Next).

    Et j'ai même essayé de ne pas remplir la liste. J'observe bien Boat::m_children::size() = 0, et dans ce cas lors de l'initialisation, on a Boat::m_children::_Myhead == Boat::m_children::_Next. C'est tout à fait logique, puisque Boat::m_children::begin() doit renvoyer la même chose que Boat::m_children::end().
    Mais même dans ce cas, suite au passage dans la boucle de remplissage de Sea::m_second_derivative, dans Sea::calculateSecondDerivative(), Boat::m_children::_Next change de valeur. Cela entraine que Boat::m_children::begin() != Boat::m_children::end() alors que Boat::m_children::size() = 0 !!!

    Donc là, je sèche à mort...

    Antoine

    PS. pour le proto, les classe sont assez mastoc et tous mes .h sont commentés pour générer une doc, donc ça prend une place monstre !!!

  9. #9
    mat.M
    Invité(e)
    Par défaut
    Citation Envoyé par VoidSeer
    Non Mat.M. for(int i=0; i < 10; ++i) et for(int i=0; i < 10; i++) donne 10 itérations avec i variant de 0 à 9.
    Si tu veux t'en convaincre, écris un programme de test
    Bon OK , Mea Culpa mais je comprends pas pq on a une fois ++i et une autre fois i++
    Cela porte à confusion et bob2356 risque de faire des erreurs dans son code

  10. #10
    Membre à l'essai
    Profil pro
    Inscrit en
    Février 2004
    Messages
    15
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2004
    Messages : 15
    Points : 10
    Points
    10
    Par défaut
    Le code n'est pas de moi uniquement, nous sommes plusieurs à travailler sur ce petit jeu, mais chacun avec nos habitudes de codage (même si on a essayé de se mettre d'accord sur une convention de codage commune).

    La préincrémentation dans les boucle for est une de mes habitudes (même si dans le cas des boucles for, i++ ou ++i génèrent le même code en assembleur avec le compilo de VC++ d'après ce que j'ai lu).

    En attendant, je rame sur mon problème... Y a-t-il un moyen de tracer les adresses mémoires occupées par un tableau aloué dynamiquement ? Je voudrais essayer de vérifier si lors du remplissage de Sea::m_second_derivative, je n'empiète pas sur la plage mémoire alouée à ma liste, mais je n'arrive pas à accéder aux adresses mémoires de ce tableau dans le debugger. Lorsque je trace Sea::m_second_derivative, j'obtient l'adresse du pointeur (puisque c'est un double *), et lorsque je trace Sea::m_second_derivative[0] (par exemple), j'obtient la valeur stockée à cet index du tableau, mais pas l'adresse mémoire correspondante.

    Quelqu'un saurait-il comment accéder à ces adresses dans le debugger de VC++ ?

    Antoine

  11. #11
    Membre confirmé
    Profil pro
    Inscrit en
    Novembre 2003
    Messages
    394
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2003
    Messages : 394
    Points : 473
    Points
    473
    Par défaut
    Avant de chercher des trucs compliqués, j'ai une question simple. N'y aurait-il pas un objet inséré dans la liste m_children qui serait défini, mais pas alloué (via un new) ?
    Il y a bien des insertions dans la liste, comme en témoigne la modification des champs de m_children (qui n'est pas une erreur AMA), le plus logique serait que l'objet censé se trouvé à l'adresse mémoire 0x503e382b n'est pas instancié.

  12. #12
    Membre à l'essai
    Profil pro
    Inscrit en
    Février 2004
    Messages
    15
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2004
    Messages : 15
    Points : 10
    Points
    10
    Par défaut
    Bonjour,

    j'aurais adoré que ce soit si simple, mais c'est une des premières choses que j'ai vérifié. Boat::m_children est rempli dans le constructeur de Boat et n'est jamais retouché ailleur dans le code (même si à terme, il s'agit d'une liste car il va y avoir des insertions et des suppressions successives).

    De plus, il serait étonnant que les adresses mémoire de Boat::m_children changent très exactement suite à l'execution d'une assignation de valeur dans un tableau à la n-3ème execution (d'après ce que j'ai trouvé) d'une boucle for, visant à remplir un tableau de double membre d'une autre classe, dans un autre objet donc !

    Et enfin, comme je l'ai dit, pour le test j'ai laissé la liste vide et même dans ce cas, les adresses de Boat::m_children::_Next et Boat::m_children::_Prev changent. Cela conduit à un état incohérent où Boat::m_children::begin() != Boat::m_children::end() alors que Boat::m_children::size() == 0. C'est ce qui me fait dire qu'il ne peut pas s'agir d'une erreur d'utilisation, mais bien d'un problème de conflit de plage mémoire.

    Arrrg ! Je suis sûr que c'est une petite bétise, c'est trop rageant !!!

    Personne ne sait pour le traçage des adresses d'un tableau aloué dynamiquement dans le debugger VC++ ?

    Antoine

    EDIT:
    J'ai trouvé d'où vient le problème... Je suis un gros boulet (c'est marrant, je dis ça à chaque fois que je trouve un erreur qui m'a occupée plusieurs jours !)...

    J'écrivais bien en dehors des bornes de Sea::m_second_derivative en fait, parce que lors de la construction de Sea, je fais
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    Sea * sea = new Sea();
    sea->setWidth(2048);
    et devinez ce que je fais dans Sea::setWidth() ?!? Je réaloue un tableau de points de contrôle contenant le nombre de points de contrôle nécessaire pour la nouvelle largeur, mais pas le tableau de la dérivée seconde.

    Désolé de vous avoir pris votre temps, et merci à tous ceux qui ont tenté de m'aider.

  13. #13
    tut
    tut est déconnecté
    Membre averti
    Avatar de tut
    Inscrit en
    Juillet 2002
    Messages
    373
    Détails du profil
    Informations forums :
    Inscription : Juillet 2002
    Messages : 373
    Points : 394
    Points
    394
    Par défaut
    qu'est-ce que tu utilises comme debugger ?
    si tu utilises Visual, tu dois pouvoir programmer un point d'arrêt sur l'écriture d'une adresse mémoire, et donc voir l'instruction qui provoque le vérolage de ta liste...
    De la même façon, tu peux dumper le contenu de la mémoire sur la zone correspondant à ton tableau, et contrôler avant / après ta boucle pour voir si ça "déborde" pas un peu.

  14. #14
    Membre à l'essai
    Profil pro
    Inscrit en
    Février 2004
    Messages
    15
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2004
    Messages : 15
    Points : 10
    Points
    10
    Par défaut
    J'utilise le debugger intégré à VC++ .NET (je ne connais pas son nom). Je me doute qu'il existe un moyen de poser un point d'arrêr sur l'écriture d'une adresse mémoire, mais ma question était plutôt : est-ce que quelqu'un sait comment faire, parce que je ne trouve pas dans la documentation.

    De toute façon, mon problème n'est plus qu'un désagréable souvenir, puisque j'ai trouvé le source du mal et que j'ai juste réaloué mon tableau correctement au bon endroit !

    Merci encore

    Antoine

  15. #15
    mat.M
    Invité(e)
    Par défaut
    Citation Envoyé par bob2356
    J'utilise le debugger intégré à VC++ .NET (je ne connais pas son nom). Je me doute qu'il existe un moyen de poser un point d'arrêr sur l'écriture d'une adresse mémoire, mais ma question était plutôt : est-ce que quelqu'un sait comment faire, parce que je ne trouve pas dans la documentation.

    De toute façon, mon problème n'est plus qu'un désagréable souvenir, puisque j'ai trouvé le source du mal et que j'ai juste réaloué mon tableau correctement au bon endroit !

    Merci encore

    Antoine

    Il y a des FAQ là-dessus il faut chercher un peu .
    Sinon pour déboguer il suffit de presser F5, mettre le projet en cible "debug" .
    La ligne sur laquelle on veut s'arrêter on presse F9 cela bascule un point d'arrêt et tu as dans une fenetre les variables locales et espions

  16. #16
    Membre à l'essai
    Profil pro
    Inscrit en
    Février 2004
    Messages
    15
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2004
    Messages : 15
    Points : 10
    Points
    10
    Par défaut
    Il y a eu un petit malentendu là ! Je connais le debugger VC++ et je m'en sert depuis le début du projet sans problème ! Je ne demande pas comment on se sert du debugger de manière générale, ça je le sais (sinon, comment aurais-je pu vous donner des informations sur les adresses mémoires incriminées dans mon problème, et l'endroit exact du problème ?!?).
    C'est juste que par défaut, le debugger n'affiche pas les adresses mémoires des cellules d'un tableau, mais juste celle du tableau lui-même, et je ne trouve pas le moyen de le faire, Y COMPRIS en ayant parcouru la documentation fournie par microsoft et divers sites internet.

    Enfin bon, le problème est résolu de toute façon, donc il n'y a plus de raison de s'en faire !

    Antoine

  17. #17
    mat.M
    Invité(e)
    Par défaut
    Citation Envoyé par bob2356
    C'est juste que par défaut, le debugger n'affiche pas les adresses mémoires des cellules d'un tableau, mais juste celle du tableau lui-même, Antoine
    Si parfaitement VC++ affiche les cellules d'un tableau ; mais il ne faut pas que les dimensions soient trop grosses.
    Par contre VC++ n'affiche pas toujours bien des données de type STL ( std::vector...) ; cela ne fonctionne pas toujours bien

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

Discussions similaires

  1. Réponses: 1
    Dernier message: 03/07/2014, 23h39
  2. Réponses: 15
    Dernier message: 01/10/2008, 00h36
  3. Réponses: 2
    Dernier message: 29/05/2008, 19h28
  4. std::list, std::vector et allocation mémoire
    Par buzzkaido dans le forum SL & STL
    Réponses: 20
    Dernier message: 15/06/2007, 16h58

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