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

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

 C Discussion :

Bug de code dont je ne trouve pas la source


Sujet :

C

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre actif
    Homme Profil pro
    Étudiant
    Inscrit en
    Mars 2013
    Messages
    54
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2013
    Messages : 54
    Par défaut Bug de code dont je ne trouve pas la source
    Bonjour,

    En désespoir de cause, je me tourne vers vous.
    Cela fait 2 jours que je m'arrache les cheveux pour trouver mon erreur et je ne comprend pas d’où elle peut venir.
    Je ne demande que l'on fasse le code a ma place, mais juste m'aider a trouver d'ou peut bien venir mon erreur..

    J'ai donc un code "tout simple" qui me sert a construire un tableau à deux dimension contenant des structures.
    Jusque la tout va bien, j'ai mon tableau, mes structures, que j'initialise et auquel je peux accéder depuis mon main sans problème.

    Le soucis vient lorsque j'essaie d'y avoir accès dans une autre fonction.
    Pour expliquer brièvement mon intention..
    Les structure contiennent entres autre les coordonnées x et y des cellules du tableau a 2 dimensions, que j'initialise lors de la création du tableau.
    Comme je le disais plus haut, depuis mon main, je n'ai pas de problème pour y accéder, et ma petite boucle de test m'affiche bien les bonnes coordonnées pour chaque cellules.

    Or, dans une autres fonction, où j'essaie aussi d'afficher ces deux valeurs de coordonnée de cellules, mon x et mon y s'inversent.. j'ai beau chercher mon erreur, une "stupide" inversion dans les variables.. je ne vois pas..
    Pour moi le code est bon, mais apparemment il ne l'est pas..

    Plutôt que de copier l’entièreté du code ici, j'ai rendu mon repo sur github public, et vous pouvez y accéder via ce lien
    BatailleNavale (vous tomber directement dans la bonne branche du repo avec le lien, sinon c'est la branche Develop v0.3.1)
    (Le projet s'appelle BatailleNavale, mais ne fait que de s'en inspirer)
    Vous pouvez forker, soumettre des commit ou push dans le repo github si vous le voulez, ou bien directement ici..

    Le fichier incriminer se nomme fct_CoordBateau.c, les lignes qui me posent problème sont la 90 et 91
    Ce fichier contient des lignes en printf qui me permettent de debugger le code et qui ne sont pas destinées a rester.
    Le code est censé me permettre de placer la case de départ d'un bateau et d'extrapoler les 4 positions (droite, haut, gauche, bas) sur le damier, le tout en tenant compte de sa taille.

    Le fichier fct_Crea_tDamier.h contient la déclaration de la structure, et fct_Crea_tDamier.c la construction des tableaux et l'initialisation de la structure avec des valeurs par défaut.
    La fonction fct_Crea_tIndexPos.h fct_Crea_tIndexPos.c ne servent a rien pour le moment.

    Si vous lancer le projet pour test, vous pouvez entrer les valeurs 10 10 5 (pour dans l'ordre, la taille du damier, la taille du bateau le plus grand, et la taille du bateau choisi).

    Si le bug est corrigé, normalement le damier (vide pour le moment) s'affiche..



    Je ne pense pas avoir a expliquer pas avant le projet, mais je peux le faire pour ceux qui voudrait en savoir plus.. il me reste encore pas mal de chose a faire, et le code du projet est encore vide de beaucoup de chose a implémenter.
    Mais je ne peux pas continuer tant que j'ai ce bug :/



    Un tout grand merci a ceux qui prendront la peine de m'aider a trouver se "'&é'"@# bug

  2. #2
    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
    Ton problème est le suivant:

    à l'appel, tu procèdes ainsi:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    int main() {
    	stCaseDamier **tDamier = NULL;
    	tDamier = fct_Crea_tDamier(size_Damier);
    	fct_CoordBateau(dTailleBateau, size_Damier, &tDamier);
    }
     
    void fct_CoordBateau(int dTailleBateau, size_t size_Damier, stCaseDamier ***tDamier) {
    	int dI_XPos_TB1 = tDamier[0][0]->dXPos - dTailleBateau + 1;
    }
    Dans main, tDamier est un pointeur de pointeur de stCaseDamier.
    Sa valeur est donc l'adresse d'une chose (ou d'un tableau) contenant l'adresse d'un (ou d'un tableau de) stCaseDamier.

    (note, l'adresse d'une chose ou d'un tableau de chose sont équivalent, pour les pointeurs)

    Tu donne à la fonction fct_CoordBateau l'adresse de tDamier.
    Dans cet appel de cette fonction, le paramètre tDamier (distinct de la variable de de main) contient l'adresse d'une chose contenant un tableau d'adresse de tableau de stCaseDamier
    Dans cette fonction, tDamier[0][0] est le premier élément du premier élément de ce triple pointeur.

    Sauf, que c'est faux.
    le vrai tableau de tableau, c'est *tDamier, l'élément est (*tDamier)[0][0].

    Pour t'éviter ce problème crée une structure de damier, et les fonctions qui vont avec:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    typedef unsigned int coordDamier;
    typedef struct {
        stCaseDamier ** cases;
        coordDamier taille;
    } stDamier;
     
    stDamier damier_creer(coordDamier taille)
    void damier_detruire(stDamier *)
    bool/int damier_accepte(stDamier const*, coordDamier ligne, coordDamier colonne)
    stCaseDamier* damier_case(stDamier *, coordDamier ligne, coordDamier colonne)
    stCaseDamier const* damier_case_const (stDamier const*, coordDamier ligne, coordDamier colonne)
    Avec ces fonctions, tu gagne en prime le bonus de pouvoir remplacer facilement la forme interne de stDamier, sans changer le reste du code.
    Notamment pour passer à un pointeur de tableau linéarisé (malloc( sizeof(stCaseDamier)*taille*taille ); et accès via cases[taille*ligne+colonne])

  3. #3
    Membre actif
    Homme Profil pro
    Étudiant
    Inscrit en
    Mars 2013
    Messages
    54
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2013
    Messages : 54
    Par défaut
    Déjà un tout grand merci pour avoir pointer mon erreur aussi vite

    Je me doutais que c’était encore ces pointeurs qui me compliquaient la vie.. décidément ces machins sont vraiment délicats et tortueux a comprendre..

    Mais je vais devoir avouer que je ne comprend pas tout ce que tu m'expliques.. est ce que tu pourrais prendre la peine de développer un peu plus ton explications stp??

    Citation Envoyé par ternel Voir le message
    Tu donne à la fonction fct_CoordBateau l'adresse de tDamier.
    Dans cet appel de cette fonction, le paramètre tDamier (distinct de la variable de de main) contient l'adresse d'une chose contenant un tableau d'adresse de tableau de stCaseDamier
    Dans cette fonction, tDamier[0][0] est le premier élément du premier élément de ce triple pointeur.

    Sauf, que c'est faux.
    le vrai tableau de tableau, c'est *tDamier, l'élément est (*tDamier)[0][0].
    Quand tu dis que j'envoie le paramètre tDamier (distinct de la variable de de main)!? Est ce que je n'envoie pas la bonne adresse?? Comment se fait il que j'arrive a accéder a mes valeurs alors??
    Moi qui pensais avoir un peu plus compris les pointeurs dans mon post précédent :/



    Citation Envoyé par ternel Voir le message
    Pour t'éviter ce problème crée une structure de damier, et les fonctions qui vont avec:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    typedef unsigned int coordDamier;
    typedef struct {
        stCaseDamier ** cases;
        coordDamier taille;
    } stDamier;
     
    stDamier damier_creer(coordDamier taille)
    void damier_detruire(stDamier *)
    bool/int damier_accepte(stDamier const*, coordDamier ligne, coordDamier colonne)
    stCaseDamier* damier_case(stDamier *, coordDamier ligne, coordDamier colonne)
    stCaseDamier const* damier_case_const (stDamier const*, coordDamier ligne, coordDamier colonne)
    Avec ces fonctions, tu gagne en prime le bonus de pouvoir remplacer facilement la forme interne de stDamier, sans changer le reste du code.
    La j'avoue, je suis complétement paumé, et je ne comprend pas


    Quel est l’intérêt de passer par un tableau linéariser?? Il me semble que ca complique l’écriture et l'utilisation dans le code..


    Est ce qu'il ne pourrait pas y avoir une solution ou je corrige "simplement" l'erreur de l'adressage du pointeur??

  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
    je vais prendre tes questions dans l'ordre inverse


    Est ce qu'il ne pourrait pas y avoir une solution ou je corrige "simplement" l'erreur de l'adressage du pointeur?
    Oui: tu comprends ce qu'est un pointeur, qui est l'adresse de quoi, et pourquoi tu utilises chaque niveau de pointage (chaque étoile).
    en l'occurence, le paramètre de ta fonction prendre l'adresse d'un double pointeur, pour pouvoir le modifier. Si ce n'est pas voulu, autant supprimer la troisième étoile (et la prise d'adresse à l'appel).

    Quel est l’intérêt de passer par un tableau linéariser?
    Un tableau T est une suite contigüe d'éléments.
    Accéder à un tel élément demande au processeur de calculer l'adresse de l'élément, en calculant l'adresse &T + i * sizeof(élément). Cette opération d'adresse peut s'écrire (T + i), car l'arithmétique des pointeurs est toujours fait en tenant compte de la taille du type pointé.
    L'accès à proprement parler à l'élément s'écrit *(T+i) ou T[i].

    Avec un deuxième niveau de pointeur, T+i est l'adresse d'un pointeur, qu'il faut encore décaler de j. L'opération effectuée est concrètement *(*(T +i) + j)Il y a deux accès à la mémoire avant d'atteindre effectivement l'élément.
    Chaque ligne est un tableau en mémoire, situés chacun n'importe où, précisément pas ensemble (puisqu'on a fait un malloc pour chacune).

    Avec un tableau linéarisé, tous les éléments se suivent: on calcule *(T +i *taille_ligne + j) et on n'a qu'une seule indirection.
    Fondamentalement, c'est déjà plus rapide.
    Pire, comme les lignes se suivent, le cache du processeur devrait être mieux utilisé, et les performances pourraient être significativement améliorées.

    La j'avoue, je suis complétement paumé, et je ne comprend pas
    L'idée, c'est que dans ton code utile, tu n'aie pas besoin des détails du damier.
    Exactement comme tu passes par fopen, fclose, fread, etc plutot que d'accéder aux détails de FILE.
    Les opérations de base du damier sont d'accéder à un élément du damier (damier_case et damier_case_const, si c'est juste pour regarder), créer et détruire un damier (qui appelle malloc/free selon les besoins). Et surtout, vérifier efficacement qu'on ne sort pas du damier.

    Quand tu dis que j'envoie le paramètre tDamier (distinct de la variable de de main)!? Est ce que je n'envoie pas la bonne adresse?? Comment se fait il que j'arrive a accéder a mes valeurs alors??
    Moi qui pensais avoir un peu plus compris les pointeurs dans mon post précédent :
    Le paramètre tDamier de la fonction est une variable appartenant à cette fonction.
    La variable locale tDamier de main lui appartient.
    Ces deux variables sont donc forcément distincte.

    Lors de l'appel de la fonction, son paramètre est créée (sur la pile), et initialisée avec l'expression donnée en argument de l'appel de fonction.
    le tDamier de la fonction est donc initialisé avec la valeur &tDamier calculée dans main() donc l'adresse du tDamier de main.
    tDamier de main est un pointeur vers tableau de pointeurs vers des tableaux de cases. C'est un damier.
    En tant que pointeur, le copier ne copie pas les tableaux pointés (c'est l'intérêt et problème des pointeurs)

    tDamier de la fonction est donc l'adresse d'un damier.
    Pour accéder à une case, il faut d'abord accéder au damier (par l'étoile), puis descendre dans le tableau de tableaux
    Donc, dans la fonction, c'est (*tDamier)[i][j] qu'il faut utiliser.

    C'est dans la fonction que tu utilises mal ton pointeur.
    Je n'ai pas regardé, mais si tu n'as pas besoin que la fonction modifie le pointeur en lui même, il est inutile de prendre un triple pointeur, un double pointeur suffit.

  5. #5
    Expert éminent

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

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 202
    Par défaut
    Prenons un exemple explicatif, en choisissant des adresses comme le ferait le système, en supposant size_Damier = 2.
    Au vue de ton code, il est très probable que sizeof(stCaseDamier) = 6*sizeof(int) = 24.

    Ton main() se déroule plus ou moins ainsi:

    déclaration de tDamier: une variable est créée sur la pile: son adresse est 32 (arbitrairement) et sa valeur est NULL (=0).

    appel de la fonction de création du damier: (tDamier = fct_Crea_tDamier(size_Damier))
    une variable est créé sur la pile, pour contenir le paramètre de la fonction. Son adresse est 36 (= 32 + sizeof(tDamier)), et sa valeur est size_Damier = 2.

    On entre dans la fonction:
    une variable tDamier est créé sur la pile: Son adresse est 36 + sizeof(size_t) = 40. sa valeur est NULL (tu l'as initialisée ainsi).
    deux autres variables non initialisées sont créées: i et j, d'adresse 44 et 48.

    Allocation du tableau de pointeurs de lignes tDamier = malloc(size_Damier * sizeof(stCaseDamier *));;
    tDamier vaut maintenant une adresse dans le tas, par exemple 1234560000.

    boucle for exécutant principalement tDamier[i] = malloc(size_Damier * sizeof(stCaseDamier)); puis l'initialisation
    tDamier[i] vaut 1234560000 + i * sizeof(*tDamier) c'est à dire 1234560000 puis 1234560000+4

    la première fois, malloc retourne arbitrairement 187365400
    on écrit le nombre 187365400 dans tDamier[0] c'est à dire à l'adresse 1234560000;
    la boucle d'initialisation va donc remplir tDamier[0][0] (@187365400+0) et tDamier[0][1] (@187365400+24)

    Au second passage, malloc retourne (toujours arbitrairement) 78965000
    on écrit le nombre 78965000 dans tDamier[1] c'est à dire à l'adresse 1234560004;
    la boucle d'initialisation va donc remplir tDamier[0][0] (@78965000+0) et tDamier[0][1] (@187365400+24)

    Enfin, return tDamier: on retourne 1234560000.

    on finit l'instruction de main:
    tDamier de main reçoit la valeur 1234560000.

    L'état de la mémoire est donc:
    • tDamier de main: adresse 32, valeur 1234560000
    • adresse 78965000, valeur <une case initialisée>
    • adresse 78965024, valeur <une case initialisée>
    • adresse 187365400, valeur <une case initialisée>
    • adresse 187365424, valeur <une case initialisée>
    • adresse 1234560000, valeur 187365400
    • adresse 1234560004, valeur 78965000

    Note: à chaque fois, plusieurs octets sont occupés, mais l'adresse est celle du premier octet de la variable. Le compilateur et le programme savent quelle taille utiliser.


    Lorsque tu appelles fct_CoordBateau(dTailleBateau, size_Damier, &tDamier);, &tDamier vaut 32.
    La signature de la fonction étant void fct_CoordBateau(int dTailleBateau, size_t size_Damier, stCaseDamier ***tDamier), cela signifie que son paramètre tDamier est initialisé avec la valeur 32.
    Le code de cette fonction commence par
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    	int i = 0;
    	int j = 0;
    	int dI_XPos_TB1 = tDamier[i][j]->dXPos - dTailleBateau + 1;
    Que vaut tDamier[i][j]->dXPos?
    tDamier vaut 32, i et j valent 0.
    tDamier[i] vaut ce que contient la case mémoire située en 32 + 0, c'est à dire 1234560000.
    tDamier[i][j] vaut ce que contient la case mémoire située en 1234560000 + 0, c'est à dire 187365400
    tDamier[i][j]-> est l'emplacement mémoire désigné par la 187365400, prêt à être décallé par la position de dXPos. c'est à dire une case initialisée.

    Là ou ca pose problème, c'est que pour toute autre valeur de i, tu regardes n'importe ou.
    par exemple, pour tDamier[1][0], on a:
    tDamier[1] vaut la valeur située 32+1*sizeof(*tDamier) = 32 + 4 = 36. Cette valeur n'est pas définie de manière correcte. En fait, il s'agit de l'emplacement de tIndexPos de main.
    tDamier[1][0] est une erreur, mais vaut en fait tIndexPos[0]
    tDamier[1][0]->dXPos est une erreur, mais vaut en fait tIndexPos[0]+le décallage de dXPos dans stCaseDamier, soit a peu près n'importe quoi.

    Plus "amusant", si tIndexPos vaut toujours NULL, tDamier[1][0] provoquera normalement un crash pour erreur de segmentation, car tu essaies d'accéder à l'adresse NULL.



    Remarque, avec un tableau linéarisé, l'initialisation donnerait quelque chose comme ceci:
    • tDamier de main: adresse 32, valeur 1234560000
    • adresse 187365400, valeur <une case initialisée>
    • adresse 187365424, valeur <une case initialisée>
    • adresse 187365448, valeur <une case initialisée>
    • adresse 187365472, valeur <une case initialisée>
    • adresse 1234560000, valeur 187365400

  6. #6
    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
    Comme tu ne modifie pas tDamier directement dans ta fonction, retire la troisième étoile, et remplace tes tDamier[][]-> par tDamier[][]..

    A présent, un gros commentaire (sous forme de question):
    Pourquoi utiliser cette notation hongroise?

    fct_coord_bateau?
    A quel moment peut-on ne pas voir que c'est une fonction? est-ce que la bibliothèque standard contient fct_fopen, fct_malloc, fct_strcmp, fct_abs, etc…
    Que fait coord_bateau? Ce n'est pas clair dans son nom.

    De même pourquoi tDamier plutot que damier?
    tu n'as pas utiliser iI et iJ que je sache?

Discussions similaires

  1. etrangeté dont je ne trouve pas la source.
    Par frp31 dans le forum Unix
    Réponses: 16
    Dernier message: 03/08/2011, 08h40
  2. Réponses: 4
    Dernier message: 17/09/2010, 22h37
  3. Réponses: 4
    Dernier message: 24/06/2009, 14h13
  4. [PostgreSQL] erreurs dont je ne trouve pas l'origine
    Par flo78 dans le forum PHP & Base de données
    Réponses: 6
    Dernier message: 20/01/2006, 13h38
  5. utiliser des classes dont on n'a pas le source
    Par kocin dans le forum Eclipse Java
    Réponses: 2
    Dernier message: 28/08/2004, 16h05

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