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 :

Masquage de pointeur pour un type abstrait de donnée


Sujet :

C

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre Expert
    Avatar de Pragmateek
    Homme Profil pro
    Formateur expert .Net/C#
    Inscrit en
    Mars 2006
    Messages
    2 635
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Formateur expert .Net/C#
    Secteur : Conseil

    Informations forums :
    Inscription : Mars 2006
    Messages : 2 635
    Par défaut Masquage de pointeur pour un type abstrait de donnée
    Bonjour,

    parfois des types abstraits de donnée sont définis ainsi :
    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
    typedef struct
    {
            int n;
    }
    *Integer;
     
    Integer newInteger(int n)
    {
            Integer i = malloc(sizeof(*i));
            i->n = n;
            return i;
    }
     
    void deleteInteger(Integer i)
    {
            free(i);
    }
     
    int getValue(Integer i)
    {
            return i->n;
    }
    et est utilisé ainsi :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    Integer i = newInteger(1);
    printf("%i\n", getValue(i));
    deleteInteger(i);
    Or il a été signalé à plusieures reprises sur ce forum que masquer un pointeur n'était pas une bonne pratique .

    Plusieures questions :
    1) Pourriez vous donner des exemples de problèmes générés par cette pratique dans le cas général, c'est à dire si l'utilisateur du type abstrait manipule directement celui ci sans passer par les fonctions qui lui sont associées.
    2) Dans le cas où l'utilisateur utilise les fonctions fournies et uniquement celles-ci pour :
    - construire le type abstrait
    - manipuler le type abstrait
    - effacer le type abstrait
    peut il y avoir encore des problèmes, si oui, lesquels ?

    Merci .

  2. #2
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 395
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 395
    Par défaut
    Eh bien, coté "utilisateur", c'est surtout que pour un TAD simple comme Integer, on peut facilement oublier d'appeler deleteInteger().

    Mais surtout, les problèmes sont au niveau du code du TAD:
    Et aussi, c'est moins lisible dans le code même du TAD (pas trop dans son utilisation).

    Il m'arrive de faire des Typedefs pointeurs, mais je ne "cache" jamais le pointeur dedans: En lisant le type, on se doute facilement que c'est un pointeur.
    Code C : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    typedef struct
    {
    	int n;
    } Integer, *PInteger;
    typedef const Integer * PCInteger;
    Tu remarqueras aussi que j'ai deux types pointeurs différents: L'un const, l'autre pas.

    Sans parler des problèmes liés aux typedefs "arbre" et "liste" définis comme de bêtes pointeurs vers des structures qui seraient mieux nommées "noeud" et "chaînon"...
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  3. #3
    Expert confirmé
    Avatar de Melem
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Janvier 2006
    Messages
    3 656
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Janvier 2006
    Messages : 3 656
    Par défaut
    Et comment tu déclares les fonctions? D'abord ta structure elle est définie dans un .c ou dans un .h? En principe, on définit la structure et les fonctions associées dans un fichier .c et on met les déclarations dans un fichier .h.

    Dans le fichier .c on devrait donc avoir quelque chose ressemblant à ceci :

    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
     
    #include <stdlib.h>
     
    struct integer
    {
            int n;
    };
     
    typedef struct integer * Integer;
     
    Integer newInteger(int n)
    {
            Integer i = malloc(sizeof(*i));
            i->n = n;
            return i;
    }
     
    void deleteInteger(Integer i)
    {
            free(i);
    }
     
    int getValue(Integer i)
    {
            return i->n;
    }
    Et dans le .h :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    #ifndef H_INTEGER
     
    #définie H_INTEGER
     
    typedef struct integer * Integer;
     
    Integer newInteger(int n);
    void deleteInteger(Integer i);
    int getValue(Integer i);
     
    #endif
    Pourriez vous donner des exemples de problèmes générés par cette pratique dans le cas général, c'est à dire si l'utilisateur du type abstrait manipule directement celui ci sans passer par les fonctions qui lui sont associées.
    En principe il ne peut pas puisque la définition de la structure est cachée. Si dans un fichier a.c on a :
    et dans a.h :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    ...
    struct A;
    ...
    On ne peut pas faire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    #include "a.h"
    ...
    struct A a;
    ...
    Car les détails de la structure ne sont pas connus (donc sa taille non plus). Par contre on peut utiliser un pointeur :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    #include "a.h"
    ...
    struct A * p;
    ...
    mais on ne pourra jamais déréférencer le pointeur (c'est-à-dire faire *p) ...

    Ce ne serait cependant pas le cas si on avait défini la structure dans a.h.

    Dans le cas où l'utilisateur utilise les fonctions fournies et uniquement celles-ci pour :
    - construire le type abstrait
    - manipuler le type abstrait
    - effacer le type abstrait
    peut il y avoir encore des problèmes, si oui, lesquels ?
    Pas très clair. S'il y a des problèmes ben c'est tout simplement parce que t'as mal codé tes fonctions sinon je ne vois pas.

    Et concernant la question masquer ou pas masquer le pointeur je crois que c'est plutôt une question de goût ou de point de vue personnel.

    Citation Envoyé par Médinoc
    Eh bien, c'est surtout que pour un TAD simple comme Integer, on peut facilement oublier d'appeler deleteInteger().
    Après un malloc il faut quand même un free ...

  4. #4
    Expert éminent
    Avatar de Emmanuel Delahaye
    Profil pro
    Retraité
    Inscrit en
    Décembre 2003
    Messages
    14 512
    Détails du profil
    Informations personnelles :
    Âge : 68
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2003
    Messages : 14 512
    Par défaut
    Citation Envoyé par seriousme Voir le message
    Or il a été signalé à plusieures reprises sur ce forum que masquer un pointeur n'était pas une bonne pratique .

    Plusieures questions :
    1) Pourriez vous donner des exemples de problèmes générés par cette pratique dans le cas général, c'est à dire si l'utilisateur du type abstrait manipule directement celui ci sans passer par les fonctions qui lui sont associées.
    Impossible. C'est le principe du TAD Les données ne sont pas accessibles de l'extérieur. L'usage des 'accesseurs' est obligatoire.

    Nota : Ceci
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    typedef struct
    {
            int n;
    }
    *Integer;
    N'est pas un TAD. Il faut séparer l'interface de l'implémentation :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    /* interface (x.h) */
    typedef struct integer *Integer;
     
    /* prototypes des fonctions ... */
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    /* implementation (x.c) */
    #include "x.h"
    struct integer
    {
       int n;
    };
     
    /* fonctions ... */
    2) Dans le cas où l'utilisateur utilise les fonctions fournies et uniquement celles-ci pour :
    - construire le type abstrait
    - manipuler le type abstrait
    - effacer le type abstrait
    peut il y avoir encore des problèmes, si oui, lesquels ?
    http://emmanuel-delahaye.developpez.com/tad.htm

  5. #5
    Membre Expert
    Avatar de Pragmateek
    Homme Profil pro
    Formateur expert .Net/C#
    Inscrit en
    Mars 2006
    Messages
    2 635
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Formateur expert .Net/C#
    Secteur : Conseil

    Informations forums :
    Inscription : Mars 2006
    Messages : 2 635
    Par défaut
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    #ifndef H_INTEGER
     
    typedef struct integer * Integer;
     
    Integer newInteger(int n);
    void deleteInteger(Integer i);
    int getValue(Integer i);
     
    #endif
    plutôt :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #ifndef H_INTEGER
    #define H_INTEGER 
     
    typedef struct integer * Integer;
     
    Integer newInteger(int n);
    void deleteInteger(Integer i);
    int getValue(Integer i);
     
    #endif
    Impossible. C'est le principe du TAD Les données ne sont pas accessibles de l'extérieur. L'usage des 'accesseurs' est obligatoire.
    En effet donc la question ne se pose même pas .

    Merci à tous .

  6. #6
    Membre Expert
    Avatar de Pragmateek
    Homme Profil pro
    Formateur expert .Net/C#
    Inscrit en
    Mars 2006
    Messages
    2 635
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Formateur expert .Net/C#
    Secteur : Conseil

    Informations forums :
    Inscription : Mars 2006
    Messages : 2 635
    Par défaut
    Une autre question en lien avec les types abstraits de donnée .
    De la même façon qu'il est possible de définir des méthodes "private" avec le mot clé "static", est-il possible d'exposer publiquement certains attributs d'un type abstrait et pas d'autres afin de séparer attributs "private" et "public" ?

    Peut être :

    Integer.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
    #ifndef H_INTEGER
    #define H_INTEGER
     
    typedef struct
    {
    	int n;
    }
    *Integer;
     
    Integer newInteger(int n);
    void deleteInteger(Integer i);
    int getValue(Integer i);
     
    #endif
    Integer.c :
    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
    #include <stdlib.h>
    #include "Integer.h"
     
    typedef struct
    {
    	int n;
    	int private_n;
    }
    integer_private, *Integer_private;
     
    static Integer setPrivateValue(Integer i, int n)
    {
    	((Integer_private)i)->private_n = n;
    	return i;
    }
     
    Integer newInteger(int n)
    {
    	Integer_private i = malloc(sizeof(*i));
    	i->n = n;
    	setPrivateValue((Integer)i, n);
    	return (Integer)i;
    }
     
    Integer setValue(Integer i, int n)
    {
    	i->n = n;
    	return i;
    }
     
    void deleteInteger(Integer i)
    {
    	free(i);
    }
     
    int getValue(Integer i)
    {
    	return i->n;
    }
    main.c :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #include <stdlib.h>
    #include <stdio.h>
    #include "Integer.h"
     
    int main(void)
    {
    	Integer i = newInteger(1);
    	printf("%i\n", i->n);
    	deleteInteger(i);
    	return EXIT_SUCCESS;
    }
    L'utilisateur du type ne peut ni accéder à la fonction "setPrivateValue" ni à "private_n".
    Ce n'est bien sûr qu'un brouillon .
    Qu'en est il de la portabilité des casts utilisés ?

    Merci .

  7. #7
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 395
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 395
    Par défaut
    Houlà, c'est crade.

    À ta place, je supposerais plutôt que non. Si tu veux obtenir un accès direct à un champ, fais une fonction qui retourne un pointeur vers celui-ci...
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  8. #8
    Expert éminent
    Avatar de Emmanuel Delahaye
    Profil pro
    Retraité
    Inscrit en
    Décembre 2003
    Messages
    14 512
    Détails du profil
    Informations personnelles :
    Âge : 68
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2003
    Messages : 14 512
    Par défaut
    Citation Envoyé par seriousme Voir le message
    De la même façon qu'il est possible de définir des méthodes "private" avec le mot clé "static", est-il possible d'exposer publiquement certains attributs d'un type abstrait et pas d'autres afin de séparer attributs "private" et "public" ?
    Les fonctions déclarées dans le .h sont publiques
    Les fonction définies dans le .c avec le mot clé 'static' sont privées.
    Il n'y a pas d'attributs 'publics', toutes les variables sont privées (c'est de bon sens...).
    Il n'y a pas d'équivalent à 'friend'.

    La solution que tu proposes ne fonctionne pas, car 2 structures différentes ne peuvent pas avoir le même nom. Par contre, il est possible de définir une structure publique 'normale' avec des éléments publics dont un ADT (pointeur, obligatoirement) qui cache les données privées.
    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
     
    /* x.h */ 
    #ifndef H_X
    #define H_X
     
    struct x_private;
     
    struct x_public
    {
        int a;
        double b;
    };
     
    struct x
    {
        struct x_public pub;
        struct x_private *priv; /* ADT */
    };
     
    /* prototypes */
     
    struct x *x_create(/* ... */);
    void x_delete(struct x * self);
     
    #endif

  9. #9
    Membre Expert
    Avatar de Pragmateek
    Homme Profil pro
    Formateur expert .Net/C#
    Inscrit en
    Mars 2006
    Messages
    2 635
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Formateur expert .Net/C#
    Secteur : Conseil

    Informations forums :
    Inscription : Mars 2006
    Messages : 2 635
    Par défaut
    La solution que tu proposes ne fonctionne pas, car 2 structures différentes ne peuvent pas avoir le même nom.
    Où ça ?

    En tout cas cela compile ainsi :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    gcc -ansi -pedantic -Wall -Wextra -c Integer.c
    gcc -ansi -pedantic -Wall -Wextra main.c Integer.o

  10. #10
    Membre chevronné
    Profil pro
    Inscrit en
    Juillet 2007
    Messages
    309
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France

    Informations forums :
    Inscription : Juillet 2007
    Messages : 309
    Par défaut
    Je tiens à contesté ça... La flexibilité du C grâce aux jeux des casts permet d'obtenir des types "partiels", des objets dont une partie de la structure est publique et l'autre pas...

    C'est peut-être pas très propre, je vais peut-être se faire dresser les cheveux sur la tête des puristes mais c'est possible. Je ne dis pas que c'est une pratique à utiliser, c'est même plutôt dangereux pour l'intégrité des objets, mais il est possible de faire ce genre de trucs, voilà une petite démo :

    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
    // MonType.h
     
    #ifndef MONTYPE_H_
    #define MONTYPE_H_
     
    typedef struct MonType {
    	int public;
    } *MonTypeRef;
     
    MonTypeRef CreateMonType(int value);
    int GetPublic(MonTypeRef o);
    int GetPrivate(MonTypeRef o);
    void FreeMonType(MonTypeRef o);
     
    #endif
    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
    // MonType.c
     
    #include "MonType.h"
    #include <stdlib.h>
     
    typedef struct {
    	int public;
    	int private;
    } MonType;
     
    MonTypeRef CreateMonType(int value)
    {
    	MonType *new = malloc(sizeof(MonType));
    	new->public = value;
    	new->private = value * 2;
     
    	return (MonTypeRef)new;
    }
     
    int GetPublic(MonTypeRef o)
    {
    	return o->public;
    }
     
    int GetPrivate(MonTypeRef o)
    {
    	return ((MonType*)o)->private;
    }
     
    void FreeMonType(MonTypeRef o)
    {
    	free(o);
    }
    Et le petit test tout simple :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #include <stdio.h>
    #include "MonType.h"
     
    int main (int argc, const char * argv[])
    {
        MonTypeRef obj = CreateMonType(5);
        printf("obj = %d | %d, %d",
               obj->public,
               GetPublic(obj), GetPrivate(obj));
        FreeMonType(obj);
        return 0;
    }
    Ce qui fait qu'on a deux champs dans cet objet, un champ public et un champ privé, et les deux cohabites très bien. Alors c'est pas forcément très propre, mais c'est possible, et il y a plein d'autres systèmes de ce genre-là permettant de masquer les données à différents niveaux.

    Par exemple, masquer un compteur de références avant les données utiles :
    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
    // MonType.h
    #ifndef MONTYPE_H_
    #define MONTYPE_H_
     
    typedef struct MonType {
    	int public;
    } *MonTypeRef;
     
    MonTypeRef CreateMonType(int value);
    int GetPublic(MonTypeRef o);
    int GetPrivate(MonTypeRef o);
    int GetCompteur(MonTypeRef o);
    void FreeMonType(MonTypeRef o);
     
    #endif
    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
    // MonType.c
    #include "MonType.h"
    #include <stdlib.h>
     
    typedef struct Compteur {
        int compteur;
    } Compteur;
     
    typedef struct {
    	int public;
    	int private;
    } MonType;
     
    MonTypeRef CreateMonType(int value)
    {
    	Compteur *new = malloc(sizeof(MonType) + sizeof(Compteur));
        new->compteur = 0;
        new = &new[1];
    	((MonType*)new)->public = value;
    	((MonType*)new)->private = value * 2;
     
    	return (MonTypeRef)new;
    }
     
    int GetPublic(MonTypeRef o)
    {
    	return o->public;
    }
     
    int GetPrivate(MonTypeRef o)
    {
    	return ((MonType*)o)->private;
    }
     
    int GetCompteur(MonTypeRef o)
    {
        return (&( (Compteur*)o )[-1])->compteur;
    }
     
    void FreeMonType(MonTypeRef o)
    {
    	free(&((Compteur*)o)[-1]);
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // main.c
    #include <stdio.h>
    #include "MonType.h"
     
    int main (int argc, const char * argv[])
    {
        MonTypeRef obj = CreateMonType(5);
        printf("obj = %d | %d, %d, %d",
               obj->public,
               GetPublic(obj), GetPrivate(obj), GetCompteur(obj));
        FreeMonType(obj);
        return 0;
    }
    Bien sûr ceci n'est qu'un exemple, il y a beaucoup de précautions à prendre dans ce genre de système, notamment pour ce qui est de l'alignement de la mémoire... Mais toujours est-il que ce genre de truc est tout à fait possible et qu'il est utile et utilisé dans certains cas.

Discussions similaires

  1. Les types abstraits de données (ADT)
    Par Emmanuel Delahaye dans le forum C
    Réponses: 5
    Dernier message: 05/06/2013, 14h59
  2. Classe : type abstrait de donnée
    Par problems99 dans le forum Langage
    Réponses: 6
    Dernier message: 11/01/2011, 13h59
  3. Réponses: 7
    Dernier message: 19/07/2010, 19h01
  4. Type abstrait de donnée
    Par mia123 dans le forum Pascal
    Réponses: 1
    Dernier message: 01/06/2007, 14h00

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