Est ce que ma liste chainee generique est bien faite?

juju0169 Messages postés 15 Date d'inscription dimanche 1 août 2004 Statut Membre Dernière intervention 28 avril 2008 - 30 sept. 2006 à 00:27
cs_AlexN Messages postés 694 Date d'inscription lundi 5 décembre 2005 Statut Membre Dernière intervention 8 janvier 2014 - 1 oct. 2006 à 16:43
    Bonsoir a tous. Je vais une nouvelle fois avoir besoin de vous.


Je viens de finir un module de liste chainee generique et je voudrais savoir s'il est bien code.


Je m'explique au dela de la simple culture generale je vais devoir
utiliser ce code pour un projet dans mes etudes et je ne voudrais pas
partir sur de mauvaise base.


Je ne demande qu'a l'ameliorer et surtour a eviter les ERREURS.

PS : desole pour la longueur


*******************************************************************************

typedef void (* Traitement)(void *);


struct Cellule

{

    void * Element;       
               
//L'element stocke dans la cellule.

    struct Cellule * Cellule_Suivante;   //Pointeur sur la cellule suivante.

    Traitement Fonction_De_Traitement;

};


struct Liste

{

    struct Cellule * Premiere_Cellule; //Pointeur sur la premiere adresse de la liste.

};

*******************************************************************************

 

struct Liste * li_Initialiser_Liste (void)


{


    struct Liste * L1;


 

    if ( !(L1 = (struct Liste *) malloc (sizeof (struct Liste))) )


    {


       perror("ERREUR");


       exit(1);


    }     

     

    L1->Premiere_Cellule = NULL;


    return L1;


}


 

void li_Ajoute_EnTete (struct Liste * L1, const void * Element, const Traitement T)


{


    struct Cellule * Nouvelle_Cellule;     

 

    if ( !(Nouvelle_Cellule = (struct Cellule *) malloc (sizeof (struct Cellule))) ) 


    {


       perror("ERREUR");


       exit(1);


    }


   


    Nouvelle_Cellule->Element = (void *) Element;

    Nouvelle_Cellule->Fonction_De_Traitement = T;


    Nouvelle_Cellule->Cellule_Suivante = L1->Premiere_Cellule;


    L1->Premiere_Cellule = Nouvelle_Cellule;

}

 

void li_Supprimer_EnTete (struct Liste * L1)


{


    struct Cellule * Pointeur_Travail = L1->Premiere_Cellule;


 

    if (li_Tester_Liste_Vide(L1))

        printf("La liste est deja vide\n");

   

    else

    {

        L1->Premiere_Cellule = Pointeur_Travail->Cellule_Suivante;


        free(Pointeur_Travail);

    }


}


void li_Afficher_Liste (const struct Liste * L1)


{


        struct Cellule * Pointeur_Travail = L1->Premiere_Cellule;


       

        if (li_Tester_Liste_Vide(L1))

            printf("La liste est vide\n");


        else

        {

            while (Pointeur_Travail != NULL)


            {


           
    Pointeur_Travail->Fonction_De_Traitement
(Pointeur_Travail->Element);


           
    Pointeur_Travail =
Pointeur_Travail->Cellule_Suivante;


            }

           

            printf("\n");

        }


}

 

void li_Afficher_Entier (const void * Element)


{


        int * Pointeur_Sur_Entier = (int *) Element;


        printf("%d, ", * Pointeur_Sur_Entier);


}


void li_Afficher_Charactere (const void * Element)


{


        char * Pointeur_Sur_Charactere = (char *) Element;


        printf("%c, ", * Pointeur_Sur_Charactere);


}


void li_Afficher_Chaine_De_Characteres (const void * Element)


{


        printf("%s , ", (char *)Element);

}


******************************************************************************

    int i;


    struct Liste  * l;

    char * T[3] = {"PAPA", "MAMAN", "BEBE"};

   

    l = li_Initialiser_Liste();


    for (i = 9; i > 0; i--)


    {     

        int * p = (int *) malloc (sizeof (int));


        *p = i;


        li_Ajoute_EnTete (l, (int *) p, (void *) li_Afficher_Entier);


    }


    li_Afficher_Liste(l);

     

    for (i = 'E'; i >= 'A'; i--)

    {

        char * c = (char *) malloc (sizeof (char));

        *c = i;

        li_Ajoute_EnTete (l, (char *) c, (void *) li_Afficher_Charactere);

    }


    li_Afficher_Liste(l);


   for (i = 0; i <= 2; i++)

        li_Ajoute_EnTete (l, (char **) T[i], (void *)

           
           
           li_Afficher_Chaine_De_Characteres);

  

   li_Afficher_Liste(l);      

******************************************************************************


Merci pour tous vos futurs conseils et j'espere ne pas m'etre prompte de section pour poster.


Bonne soiree

6 réponses

cs_Joky Messages postés 1787 Date d'inscription lundi 22 novembre 2004 Statut Membre Dernière intervention 31 janvier 2009 2
30 sept. 2006 à 11:48
Tu alloues de la mémoire avec malloc mais tu libères rien... y'a un souci quelque part :)

ULARGE_INTEGERJokyQi=QI("Joky"
0
cs_AlexN Messages postés 694 Date d'inscription lundi 5 décembre 2005 Statut Membre Dernière intervention 8 janvier 2014 19
30 sept. 2006 à 16:02
Ta liste va poser des problèmes de mémoire.

Tu fais des malloc (int) et des malloc (char) mais tu ne fais pas les free() qui vont avec. Même si tu libères ta liste (Supprimer), des blocs de mémoires vont restés occupés par des résidus de ta liste.

Ta fonction supprimer() peut faire des free() sur des chaînes de caractères ou des structures qui n'auront pas été allouées sur le tas par malloc(), calloc() ou realloc(). Ton programme peut avoir des comportements indeterminés.

Il faut par exemple rajouter un champ dans la Cellule
int ALiberer;
pour que la fonction Supprimer() sache si elle doit libérer ou non ce qui ce trouve pointé par Element.

Tester les appels système est un bon reflexe et ne pas croire que parce qu'on invoque malloc() ça réussi à tous les coups.

perror() affiche un message d'erreur plus clair qu'un simple code d'erreur, mais le paramètre "ERREUR" est trop succint. perror("malloc(main)"), perror("free(Supprimer)"), etc permet de localiser plus facilement une erreur et d'éviter de se servir d'un debugger pour localiser simplement un free() oublié dans un programme de quelques lignes.
0
juju0169 Messages postés 15 Date d'inscription dimanche 1 août 2004 Statut Membre Dernière intervention 28 avril 2008
30 sept. 2006 à 18:49
OK merci pour ces premiers conseils ils sont les biens venus

Par contre il y a surtout deux trucs qui me travaille.

1er truc *********************************************************************

dans ma fonction ajouter est ce que cela suffit de faire

Nouvelle_Cellule->Element = (void *) Element;

car si au depart ça me semble correct je me suis rendu compte que j'avais un probleme quand j'utilise ma liste comme ceci

struct Liste * l;

l = li_Initialiser_Liste();

int * p = (int *) malloc (sizeof (int));
*p = 10;
li_Ajoute_EnTete (l, (int *) p, (void *) li_Afficher_Entier);
*p =12;
li_Ajoute_EnTete (l, (int *) p, (void *) li_Afficher_Entier);


Est bien la je me retrouve avec une liste 12, 12 (le dernier element apparait dans toute ma liste

c est a dire si j'insere 10 elements (Nouvelle_Cellule->Element = (void *) Element) de cette maniere la liste sera
9, 9 ,9 9 ,9 9,9...9,

Ne faut il pas plutot utiliser une fonction clone comme gere?
Nouvelle_Cellule->Element = clone (Element);

Ou alors je fais bien d'utiliser une nouvelle variable a chaque fois

2ieme truc*********************************************************************

SI j'ai bien compris les indications que j'avais pour faire cette liste il est impossible de faire

Ajouter_Entete(l, p, (void *)li_Afficher_Entier);

ou p serait un simple int p = 10;
ou p char p = 'E";


Encore merci pour toutes vos reponses je cherche vraiment a faire le truc le plus propre (au niveau code) et le plus efficace au niveau implantion et surtout ne pas passer a coter de quelque chose.

Bonne soiree
0
cs_AlexN Messages postés 694 Date d'inscription lundi 5 décembre 2005 Statut Membre Dernière intervention 8 janvier 2014 19
1 oct. 2006 à 15:06
la différence entre (1)

for (i = 9; i > 0; i--)
{     
   int * p = (int *) malloc (sizeof (int));
   *p = i;
   li_Ajoute_EnTete (l, (int *) p, (void *) li_Afficher_Entier);
}

et (2)

int * p = (int *) malloc (sizeof (int));
*p = 10;
li_Ajoute_EnTete (l, (int *) p, (void *) li_Afficher_Entier);
*p =12;
li_Ajoute_EnTete (l, (int *) p, (void *) li_Afficher_Entier);

est que dans le premier cas tu alloues dix blocs de mémoires différents que tu remplis chacun avec une valeur différentes (de 9 à 1), tandis que dans le second cas tu n'alloues qu'un seul bloc de mémoire auquel tu affectes differentes valeurs (10 puis 12).
Or ce que tu passes à la fonction Ajouter(), ce n'est pas la valeur elle même, mais l'adresse (la valeur du pointeur p) du bloc mémoire contenant cette valeur. La fonction Ajouter() ne recopie pas cette valeur mais recopie seulement l'adresse du bloc contenant cette donnée. Comme dans le second c'est toujours le même bloc donc toujours la même adresse (ou la même valeur du pointeur p), toutes les cellules pointent vers le même bloc mémoire. La valeur (la dernière que tu auras mise dans le bloc pointé par p) affichée est alors toujours la même puisqu'il s'agit du même bloc.

La fonction clone() n'est pas un standard du c, mais une fonction de l'api linux qui concerne la programmation multiprocessus, elle n'a rien à voir avec la gestion de la mémoire (malloc, calloc, realloc, free..).

Finalement voici l'adresse d'un tutorial vidéo sur les pointeurs qui est dans la bibliothèque de l'université de standford. Malgré le fait que ce tutorial ait l'air simpliste, il contient les notions essentielles qu'il faut comprendre pour maîtriser cette notion. Il vaut mieux le regarder plusieurs fois avant d'être sur d'avoir compris. http://cslibrary.stanford.edu/104/

2ieme truc*********************************************************************

Si c'est possible, parce que lorsque tu écris :
int p1 = 10;
Ajouter(p1);
int p2 = 11;
Ajouter(p2);
char c1 = 'E';
Ajouter(c1);
A chaque déclaration, une zone est reservée dans la pile d'exécution du programme pour contenir la valeur déclarée. Et chacune de tes cellules pointera sur une zone distincte dans cette pile.
Le seul problème que tu auras c'est qu'ayant déjà déclaré p comme int, le compilateur refusera que tu le redéclares en char. (d'où les p1, p2...).

Enfin une petite remarque sur la concision des écritures. Le langage c est un langage concis, qui permet d'écrire beaucoup de chose avec peu de symbôles. Il n'est pas nécessaire de faire du Baudelaire ou du Proust quand on écrit en c et c'est même nuisible à la lisibilité. J'aurais plutôt écris des nom de fonction comme Ajouter() ou AjouterListe() et des nom de variables comme cell plutôt que Pointeur_Travail. Mais il ne faut pas être trop court non plus. Un nom de  fonction comme a() ou b() ça ne veut rien dire.

Certaines variables sont inutiles :
void li_Afficher_Charactere (const void * Element)
{
        char * Pointeur_Sur_Charactere = (char *) Element;
        printf("%c, ", * Pointeur_Sur_Charactere);
}
peut devenir :
void Afficherc (const void * e) { printf("%c, ", (char *) e);  }

Les boucle for sont très puissantes et très utiles :

void li_Afficher_Liste (const struct Liste * L1)
{
        struct Cellule * Pointeur_Travail = L1->Premiere_Cellule;
       
        if (li_Tester_Liste_Vide(L1))
            printf("La liste est vide\n");

        else
        {
            while (Pointeur_Travail != NULL)
            {
                Pointeur_Travail->Fonction_De_Traitement (Pointeur_Travail->Element);
                Pointeur_Travail = Pointeur_Travail->Cellule_Suivante;
            }
           
            printf("\n");
        }
}

pourrait devenir :

void li_Afficher_Liste (const struct Liste * L1)
{
        struct Cellule * cell;
       
        if (li_Tester_Liste_Vide(L1))
            printf("La liste est vide\n");
        else {                 for ( cell L1->Premiere_Cellule; cell; cell cell->Cellule_Suivante)
                        cell->Fonction_De_Traitement (cell->Element);
                puts("");
        }
}

Maintenant faire du générique avec le c, c'est un peu de la haute volée, c++ est plus adapté.
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
juju0169 Messages postés 15 Date d'inscription dimanche 1 août 2004 Statut Membre Dernière intervention 28 avril 2008
1 oct. 2006 à 15:23
Merci AlexN pour ces nouvelles infos

Pour ce qui conserne la fonction Clone je ne savez meme pas qu'il existait une fonction comme ca je voulais en cree une a moi.

Et en ce qui conserne le langage c'est quelque chose qui met impose.

Par contre j'ai cree cette fonction clone alors voici les modifs.

Et cette fois ci si je ne me trompe pas on peut utiliser la meme variable pour ajouter
(du genre int * p1= (int *) malloc ....
*p1 = 15;
ajout(..,*p1, ....)
*p1 = 20;
ajout(..,*p1, ....)

et on a bien 20, 15 et plus 20,20 )

PS : La version que je poste date d'avant les conseils, donc pas de panique vous conseils ne sont pas tombes dans l'oreille d'un sourd.

*****************************************************************************
typedef void * (* Ajouter) (void *);
typedef void (* Traiter)(void *);

struct Cellule
{
void * Element;
struct Cellule * Cellule_Suivante;
Traiter Fonction_De_Traitement;
Ajouter Fonction_D_Ajout;
};

struct Liste
{
struct Cellule * Premiere_Cellule; //Pointeur sur la premiere adresse de la liste.
};

******************************************************************************
void li_Ajouter_EnTete (struct Liste * L1, const void * Element, const Traiter Traite, const Ajouter Ajoute)
{
struct Cellule * Nouvelle_Cellule;

if ( !(Nouvelle_Cellule = (struct Cellule *) malloc (sizeof (struct Cellule))) )
{
perror("ERREUR");
exit(1);
}

Nouvelle_Cellule->Fonction_D_Ajout = Ajoute;
Nouvelle_Cellule->Fonction_De_Traitement = Traite;
Nouvelle_Cellule->Element = Nouvelle_Cellule->Fonction_D_Ajout( (void *) Element);

Nouvelle_Cellule->Cellule_Suivante = L1->Premiere_Cellule;
L1->Premiere_Cellule = Nouvelle_Cellule;
}

int * li_Ajouter_Entier (const void * Element)
{
int * Pointeur_Entier;

if ( !(Pointeur_Entier = (int *) malloc (sizeof (int))) )
{
perror("ERREUR");
exit(1);
}

*Pointeur_Entier = * (int *)Element;
return Pointeur_Entier;
}

char * li_Ajouter_Charactere (const void * Element)
{
char * Pointeur_Charactere;

if ( !(Pointeur_Charactere = (char *) malloc (sizeof (char))) )
{
perror("ERREUR");
exit(1);
}

*Pointeur_Charactere = * (char *)Element;
return Pointeur_Charactere;
}

char ** li_Ajouter_Chaine_De_Characteres (const void * Element)
{
char ** Pointeur_Chaine_Characteres;

if ( !(Pointeur_Chaine_Characteres = (char **) malloc ( (4 * sizeof (char )) )) )
{
perror("ERREUR");
exit(1);
}

*Pointeur_Chaine_Characteres = * (char **)Element;
return Pointeur_Chaine_Characteres;
}
0
cs_AlexN Messages postés 694 Date d'inscription lundi 5 décembre 2005 Statut Membre Dernière intervention 8 janvier 2014 19
1 oct. 2006 à 16:43
Oui, c'est une bonne solution, elle consiste à créer un nouveau bloc et à y recopier le contenu du bloc passé en argument pour devenir une donnée propre à la liste. Mais incomplète pour les chaînes de caractères :
En fait lorsque tu écris :
*Pointeur_Entier = * (int *)Element;
le contenu du bloc pointé par Element est recopié dans le bloc pointé par Pointeur_Entier, parce que ce sont des types simples, et que l'affectation provoque en interne la recopie du contenu du bloc. Il se passe un peu l'équivalent de :
memcpy ((void *)Pointeur_Entier, (void *) Element, sizeof (int));
C'est la même chose avec
*Pointeur_Charactere = * (char *)Element;
qui provoque l'équivalent de :
memcpy ((void *)Pointeur_Charactere, (void *) Element, sizeof (char));


Mais :
*Pointeur_Chaine_Characteres = * (char **)Element;
ne provoque que la recopie d'une adresse d'un pointeur dans un autre. Pas les données elles même.

Un autre problème avec les chaines de caractères :
if ( !(Pointeur_Chaine_Characteres = (char **) malloc ( (4 * sizeof (char ))    )) )
Pour toutes les chaînes. tu n'alloues un bloc que pour quatre caractères (3 plus le '\0')
Tes chaînes de caractères vont déborder. Il vaut mieux écrire quelquechose comme :
if ( ! (Pointeur_Chaine_Characteres = (char *) malloc ( strlen((char *)Element) + 1) ))

Enfin les pointeurs de pointeur sont plutôt utiles quand on utilise des tableaux.

char * li_Ajouter_Chaine_De_Characteres (const void * Element)
{
char * Pointeur_Chaine_Characteres;

if ( ! (Pointeur_Chaine_Characteres = (char *) malloc(strlen((char *) Element) + 1) ))
{
perror("ERREUR");
exit(1);
}
if ( ! strcpy (Pointeur_Chaine_Characteres, (char *)Element) )
{

perror("strcpy()");

exit(1);

}

return Pointeur_Chaine_Characteres;
}

Finalement tu n'auras pas besoin de fonction clone() puisque la recopie des données sera assurée par les fonctions Ajouter().

Tu es en train de faire du pseudo objet (générique) avec du c. C'est un bon exercice qui permet entre autre de bien comprendre les pointeurs, mais aussi pourquoi le c++ a été créé. Tes Cellules contiennent chacune des pointeurs vers les fonctions capables de gérer (afficher, ajouter, etc) le type contenu dans la cellule (les fonctions membres). En c++, pour simplifier le tout et éviter d'avoir 15 pointeurs de fonctions dans chaque cellule (objet), chaque objet contient un pointeur vers sa classe qui est chargée d'appeler la fonction adaptée (les fonctions virtuelles) au type (classe) contenu dans la Cellule (objet).
0
Rejoignez-nous