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 :

Structures et alignement de données


Sujet :

C

  1. #1
    Inactif  


    Homme Profil pro
    Doctorant sécurité informatique — Diplômé master Droit/Économie/Gestion
    Inscrit en
    Décembre 2011
    Messages
    9 026
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : France, Loire (Rhône Alpes)

    Informations professionnelles :
    Activité : Doctorant sécurité informatique — Diplômé master Droit/Économie/Gestion
    Secteur : Enseignement

    Informations forums :
    Inscription : Décembre 2011
    Messages : 9 026
    Par défaut Structures et alignement de données
    Bonjour,

    Dans un projet, des consignes nous imposent de "masquer l'implémentation des structures".

    Mon binôme est arrivé à une structure de ce genre (on utilise des pointeurs de fonctions pour des raisons bien particulières) :

    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
    typedef struct ImplEnsembleColore {
      TYPE * (* next)(GoshIterateur *, struct ImplEnsembleColore *, TYPE *);
      GoshIterateur(*createIterateur)(void);
      bool (*vide)(struct ImplEnsembleColore *);
      void (*ajouter)(struct ImplEnsembleColore *, TYPE);
      bool (*appartient)(struct ImplEnsembleColore *, TYPE);
     
    #ifdef SHOW_IMPLEMENTATION_ENSEMBLE_COLORE
     
     EnsemblePosition positions;
     Couleur couleur;
     
    #endif
     
    } * EnsembleColore;
    Dans le .c, SHOW_IMPLEMENTATION_ENSEMBLE_COLORE sera défini et on pourra accéder à tous les champs.

    Ailleurs, on ne pourra récupérer que les pointeurs de fonctions (les autres champs n'étant pas définis) de cette manière (EnsembleColore ne sera d'ailleurs jamais déférencé) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    EnsembleColore foo;
    // ....
    foo->unDesPointeurs();
    A mon grand étonnement, ce code compile ( j'étais pourtant sûr que l'éditeur de liens proférerait quelques insultes) mais je me demande s'il ne peut pas y avoir des problèmes avec "l'alignement".
    En effet, dans une structure, tous les éléments sont dans l'ordre en mémoire mais pas forcément consécutif, le compilateur pouvant rajouter des octets de padding pour des raisons d'optimisations.

    J'aimerai donc savoir, même si ce code fonctionne, si le comportement est bien défini par la norme.

  2. #2
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Chercheur d'emploi
    Inscrit en
    Septembre 2007
    Messages
    7 504
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur d'emploi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 504
    Par défaut
    Hello,

    On ne le dira jamais assez : cacher un pointeur dans un typedef, c'est mal. Le langage C n'est pas le Java et on ne peut pas simuler efficacement le comportement de l'un avec l'autre.

    À part cela, je ne comprends pas vraiment ce qui te pose problème. L'alignement et le padding n'entrent pas en ligne de compte si tu accèdes aux membres d'une structure par leur nom (et il n'y a pas de raison de le faire autrement) et, dans ton cas, tes pointeurs de fonctions sont en dehors de la compilation conditionnelle, donc toujours visibles, comme tu nous l'expliques. Je ne vois pas pourquoi l'éditeur de liens trouverait à y redire.

    EN REVANCHE, ta compilation conditionnelle semble être une façon maladroite d'implémenter la notion de membres privés mais, malheureusement, toi et ton binôme ne peuvent absolument pas procéder de cette façon car la longueur de votre structure sera différente d'une unité de compilation à l'autre. Si une routine de votre programme renvoie un tableau de structure et que vous l'indexez à partir d'une autre unité, vous allez vous retrouver à cheval sur deux éléments consécutifs en mémoire et faire face à une très grosse corruption de données.

  3. #3
    Inactif  


    Homme Profil pro
    Doctorant sécurité informatique — Diplômé master Droit/Économie/Gestion
    Inscrit en
    Décembre 2011
    Messages
    9 026
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : France, Loire (Rhône Alpes)

    Informations professionnelles :
    Activité : Doctorant sécurité informatique — Diplômé master Droit/Économie/Gestion
    Secteur : Enseignement

    Informations forums :
    Inscription : Décembre 2011
    Messages : 9 026
    Par défaut
    Citation Envoyé par Obsidian Voir le message
    On ne le dira jamais assez : cacher un pointeur dans un typedef, c'est mal. Le langage C n'est pas le Java et on ne peut pas simuler efficacement le comportement de l'un avec l'autre.
    On sait, ne remue pas le couteau dans la plaie
    Consigne du prof

    Je ne vois pas pourquoi l'éditeur de liens trouverait à y redire.
    Comme on déclare deux structures de même nom avec un contenu différent, je pensais qu'il crierait au blasphème.

    L'alignement et le padding n'entrent pas en ligne de compte si tu accèdes aux membres d'une structure par leur nom (et il n'y a pas de raison de le faire autrement) et, dans ton cas, tes pointeurs de fonctions sont en dehors de la compilation conditionnelle, donc toujours visibles, comme tu nous l'expliques.
    Mais le problème, c'est qu'on a au final deux structures avec un contenu différent : A et B.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    A a;
    a->pointeurFonction = foo;
    //....
     
    B b = a;
    b->pointeurFonction();
    Est-ce qu'on est sûr à 100% que "pointeurFonction" est à la même place en mémoire dans les deux structures et qu'il n'aurait pas été "décalé" ?


    EN REVANCHE, ta compilation conditionnelle semble être une façon maladroite d'implémenter la notion de membres privés mais, malheureusement, toi et ton binôme ne peuvent absolument pas procéder de cette façon car la longueur de votre structure sera différente d'une unité de compilation à l'autre. Si une routine de votre programme renvoie un tableau de structure et que vous l'indexez à partir d'une autre unité, vous allez vous retrouver à cheval sur deux éléments consécutifs en mémoire et faire face à une très grosse corruption de données.
    On instancie, détruit et manipule les instances que dans le .c où SHOW_IMPLEMENTATION_ENSEMBLE_COLORE est défini.
    Ailleurs, on ne manipule que des pointeurs.
    Donc ça devrait être "bon" ?

  4. #4
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2009
    Messages
    4 498
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Loire Atlantique (Pays de la Loire)

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

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 498
    Billets dans le blog
    1
    Par défaut
    On instancie, détruit et manipule les instances que dans le .c où SHOW_IMPLEMENTATION_ENSEMBLE_COLORE est défini.
    Ailleurs, on ne manipule que des pointeurs.
    Donc ça devrait être "bon" ?
    Pour moi, ça signifie que ton mode opératoire n'est pas robuste. C'est trop compliqué d'expliquer à l'utilisateur de ton module "alors, il faut bien définit la macro et faire attention à ne pas mixer avec ce qui vient d'un fichier où elle ne serait pas définie". Trop de risque, trop de problème.

    Ne serait-il pas mieux de créer une type opaque et de l'utiliser à la place de ce qui est entre la compilation conditionnelle (qu'on vire au passage) ? Ainsi, tu aurais la même structure partout, mais c'est seulement à l'endroit où le type opaque sera défini que tu pourras faire des opérations sur "l'implémentation" ?
    http://en.wikipedia.org/wiki/Opaque_pointer
    http://en.wikipedia.org/wiki/Opaque_data_type

    Pour ce qui est du padding et donc de l'alignement, je suis comme Obsidian : je m'inquiète surtout par des contenus non compatibles des structures.
    Est-ce qu'on est sûr à 100% que "pointeurFonction" est à la même place en mémoire dans les deux structures et qu'il n'aurait pas été "décalé" ?
    Je pense que quand tu fais ça, le padding et l'alignement ne rentre pas en compte. Tu accèdes à un champ sans savoir où il est dans la structure.


    PS : je viens de faire un test avec des typedef avec le même alias mais pas les mêmes types réels.

    Code fichier1.c : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    typedef const char* TYPE;
     
    /*extern*/ void print(TYPE t);
     
    int main (void)
    {
        TYPE t = "bonjour le monde";
        print(t);
        return 0;
    }

    Code fichier2.c : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    typedef long TYPE;
     
    void print(TYPE t)
    {
        printf("%d %X %s\n", t, t, t);
    }
    Le compilateur est d'accord. Je crois que je vais enquêter dans la norme à ce sujet ^^

  5. #5
    Inactif  


    Homme Profil pro
    Doctorant sécurité informatique — Diplômé master Droit/Économie/Gestion
    Inscrit en
    Décembre 2011
    Messages
    9 026
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : France, Loire (Rhône Alpes)

    Informations professionnelles :
    Activité : Doctorant sécurité informatique — Diplômé master Droit/Économie/Gestion
    Secteur : Enseignement

    Informations forums :
    Inscription : Décembre 2011
    Messages : 9 026
    Par défaut
    Citation Envoyé par Bktero Voir le message
    Ne serait-il pas mieux de créer une type opaque et de l'utiliser à la place de ce qui est entre la compilation conditionnelle (qu'on vire au passage) ? Ainsi, tu aurais la même structure partout, mais c'est seulement à l'endroit où le type opaque sera défini que tu pourras faire des opérations sur "l'implémentation" ?
    http://en.wikipedia.org/wiki/Opaque_pointer
    http://en.wikipedia.org/wiki/Opaque_data_type
    Le problème c'est que j'ai besoin d'accéder aux pointeurs sur fonctions à l'extérieur du .c


    Je pense que quand tu fais ça, le padding et l'alignement ne rentre pas en compte. Tu accèdes à un champ sans savoir où il est dans la structure.
    Mais ne faut-il pas que les deux champs soient au "même endroit" dans les deux structures ?
    Si dans struct A il est en 0x04
    Et dans struct B en 0x00
    Faire a->foo = foo; puis b->foo(); n'appellera pas la bonne fonction non?

  6. #6
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2009
    Messages
    4 498
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Loire Atlantique (Pays de la Loire)

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

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 498
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par Neckara Voir le message
    Le problème c'est que j'ai besoin d'accéder aux pointeurs sur fonctions à l'extérieur du .c
    Je pensais en fait à :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    typedef struct
    {
        // pointeurs sur fonction
        void (*f)(void);
     
        // le type opaque;
        OPAQUE *p;
    } ElementColore;

    Citation Envoyé par Neckara Voir le message
    Mais ne faut-il pas que les deux champs soient au "même endroit" dans les deux structures ?
    Si dans struct A il est en 0x04
    Et dans struct B en 0x00
    Faire a->foo = foo; puis b->foo(); n'appellera pas la bonne fonction non?
    Ce que tu mets en évidence, c'est bien que tes structures ne sont pas identiques. Et si elles ne sont pas identiques, elles sont sûrement incompatible. Je pense que cela dépend de la taille des éléments manquants. Selon si leur taille est inférieure ou supérieure à celle d'un pointeur de fonction, l'alignement peut changer. Je ne sais pas si l'appel fonctionnera, il faudrait tester.

    PS :
    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
    #include <stdio.h>
     
    typedef struct
    {
        void (*f)(void);
    } TYPE;
     
    /*extern*/ void print(TYPE t);
    /*extern*/ TYPE get(void);
     
    void aFunction(void)
    {
        printf("I am %s()\n", __func__);
    }
     
    int main (void)
    {
        printf("aFunction = %p\n", aFunction);
     
        TYPE here = {aFunction};
        print(here);
     
        TYPE stranger = get();
     
        // Les lignes suivantes generent des erreurs a l'execution
        print(stranger);
        stranger.f = aFunction;
        print(stranger);
        stranger.f();
     
        return 0;
    }
    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
    #include <stdio.h>
    typedef struct
    {
        double number;
        void (*f)(void);
    } TYPE;
     
    void print(TYPE t)
    {
        puts("** TYPE **");
        printf("number = %ld\n", t.number);
        printf("f() = %p\n", t.f);
        puts("** **** **");
    }
     
    TYPE get(void)
    {
        return (TYPE) {666, NULL};
    }
    Pas de message à la compilation, mais ça se vautre à l’exécution.

  7. #7
    Inactif  


    Homme Profil pro
    Doctorant sécurité informatique — Diplômé master Droit/Économie/Gestion
    Inscrit en
    Décembre 2011
    Messages
    9 026
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : France, Loire (Rhône Alpes)

    Informations professionnelles :
    Activité : Doctorant sécurité informatique — Diplômé master Droit/Économie/Gestion
    Secteur : Enseignement

    Informations forums :
    Inscription : Décembre 2011
    Messages : 9 026
    Par défaut
    Citation Envoyé par Bktero Voir le message
    Je pensais en fait à :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    typedef struct
    {
        // pointeurs sur fonction
        void (*f)(void);
     
        // le type opaque;
        OPAQUE *p;
    } ElementColore;
    C'est ce que j'essayais de lui proposer mais j'arrive pas à le convaincre

    Les consignes nous imposent d'utiliser des pointeurs. Je n'arrive pas à lui faire comprendre que ElementColore est une forme de pointeurs (à l'image des pointeurs intelligent du C++).

    Pour ton exemple qui crash, ça ne marche pas vu que TYPE n'est pas un pointeur.

  8. #8
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2009
    Messages
    4 498
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Loire Atlantique (Pays de la Loire)

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

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 498
    Billets dans le blog
    1
    Par défaut
    "Lui", c'est ton binôme ?

    Pour ton exemple qui crash, ça ne marche pas vu que TYPE n'est pas un pointeur.
    Si je fais here.f();, ça ne crashe pas et appelle bien la fonction. Je ne maitrise pas trop le sujet (je ne fais pas de structures avec des pointeurs sur fonctions moi XD), pourquoi faudrait-il que TYPE soit un pointeur ?

  9. #9
    Inactif  


    Homme Profil pro
    Doctorant sécurité informatique — Diplômé master Droit/Économie/Gestion
    Inscrit en
    Décembre 2011
    Messages
    9 026
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : France, Loire (Rhône Alpes)

    Informations professionnelles :
    Activité : Doctorant sécurité informatique — Diplômé master Droit/Économie/Gestion
    Secteur : Enseignement

    Informations forums :
    Inscription : Décembre 2011
    Messages : 9 026
    Par défaut
    Citation Envoyé par Bktero Voir le message
    "Lui", c'est ton binôme ?
    Oui.

    Citation Envoyé par Bktero Voir le message
    pourquoi faudrait-il que TYPE soit un pointeur ?
    Consigne du prof.

    Mais c'est bon, j'ai réussi à le convaincre \o/.
    Par contre, je serais bien intéressé pour savoir si au niveau de la norme, c'est censé marcher ou non.

  10. #10
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2009
    Messages
    4 498
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Loire Atlantique (Pays de la Loire)

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

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 498
    Billets dans le blog
    1
    Par défaut
    Consigne du prof.
    On a du mal se comprendre. Dans la phrase suivante, "ça ne marche" signifie "ça crashe à cause de ça" ou "ça ne répond pas à la consigne" ?
    Pour ton exemple qui crash, ça ne marche pas vu que TYPE n'est pas un pointeur.

  11. #11
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 398
    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 398
    Par défaut
    Ce qu'il vous faut, c'est ne pas pouvoir utiliser le type directement.
    Par exemple, une manière inspirée de COM, et adaptée au C où l'héritage manque:
    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
    /*IEnsembleColore.h*/
    struct IEnsembleColore;
     
    /*Ceci est mon hack qui permet d'avoir les bons pointeurs dans la vtable
      sans trop faire de casts*/
    #ifdef IMPLEMENTATION_IEnsembleColore
    #define THIS struct ImplIEnsembleColore*
    #else
    #define THIS struct IEnsembleColore*
    #endif
     
    struct IEnsembleColoreVtbl {
      TYPE * (* next)(THIS, GoshIterateur *, TYPE *);
      GoshIterateur(*createIterateur)(THIS);
      bool (*vide)(THIS);
      void (*ajouter)(THIS, TYPE);
      bool (*appartient)(THIS, TYPE);
    };
    #undef THIS
     
    typedef struct IEnsembleColore { struct IEnsembleColoreVtbl* pVtbl; } IEnsembleColore;
     
    IEnsembleColore* CreateEnsembleColore(/* paramètres */);
    Et l'implémentation:
    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
    /*ImplIEnsembleColore.c*/
     
    struct ImplIEnsembleColore;
    #define IMPLEMENTATION_IEnsembleColore
    #include "IEnsembleColore.h" 
     
    struct ImplIEnsembleColore
    {
    	IEnsembleColore iface; /*Premier membre!*/
     
    	EnsemblePosition positions;
    	Couleur couleur;
    };
     
    TYPE* IEnsembleColore_next(struct ImplIEnsembleColore* pThis, GoshIterateur * pIt, TYPE* p)
    {
    	/*...*/
    }
    bool IEnsembleColore_vide(struct ImplIEnsembleColore* pThis)
    {
    	/*...*/
    }
    void IEnsembleColore_ajouter(struct ImplIEnsembleColore* pThis, TYPE val)
    {
    	/*...*/
    }
    bool IEnsembleColore_appartient(struct ImplIEnsembleColore* pThis, TYPE val)
    {
    	/*...*/
    }
    struct IEnsembleColoreVtbl g_IEnsembleColoreVtbl = {
    	IEnsembleColore_next,
    	IEnsembleColore_vide,
    	IEnsembleColore_ajouter,
    	IEnsembleColore_appartient
    };
     
    struct IEnsembleColore* CreateEnsembleColore(/* paramètres */)
    {
    	struct ImplIEnsembleColore* pThis = malloc(sizeof *pThis);
    	/*initialisation*/
    	pThis->iface.pVtbl = g_IEnsembleColoreVtbl;
    	/*...*/
     
    	return &pThis->iface;
    }
    Ainsi, si le code client ne passe pas par CreateEnsembleColore, il n'obtient pas de pointeur sur la vtable, et ne peut donc appeler aucune fonction sur son interface invalide.
    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.

  12. #12
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Chercheur d'emploi
    Inscrit en
    Septembre 2007
    Messages
    7 504
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur d'emploi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 504
    Par défaut
    Citation Envoyé par Neckara Voir le message
    Comme on déclare deux structures de même nom avec un contenu différent, je pensais qu'il crierait au blasphème.
    Le compilateur ne criera pas au blasphème parce qu'il compile un seul *.c à la fois et n'a donc pas de moyen de savoir a priori qu'il va en compiler un autre ailleurs avec une même définition.

    L'éditeur de liens, lui, ne criera pas non plus car la définition de la structure elle-même est abstraite et ne se retrouve pas du tout dans l'exécutable final. Seules les instances de ta structure peuvent l'être et, là encore, uniquement si elles sont globales et que leurs symboles sont exportés. Si tu n'as aucune variable globale et que tout est dynamique, renvoyé à l'exécution par des accesseurs (tes fonctions), il n'y a aucun raison pour que l'éditeur de liens voit passer quoi que ce soit, a fortiori si, dans les faits, tes structures sont allouées avec des malloc().

    Mais le problème, c'est qu'on a au final deux structures avec un contenu différent : A et B.
    La bonne nouvelle, c'est que la norme C impose que le padding éventuel soit ajouté après les membres, c'est-à-dire que tu peux en trouver entre deux membres et à la fin, mais jamais au début. L'adresse du premier membre d'une structure est donc forcément l'adresse de la structure elle-même.

    Elle impose également que l'ordre des membres dans ta structure corresponde à celui de la déclaration, donc les membres communs de ta structure déclarés avant ton bloc de compilation conditionnelles seront bien les mêmes partout.

    C'est pratique parce que ça permet d'implémenter de façon sûre les XEvent de la XLib, par exemple. Ces Event reconstruisent en C un forme d'héritage objet : leur premier champ est toujours un entier nommé « type » qui précise l'événement dont il s'agit réellement. Il est donc sûr de transtyper le pointeur vers une autre structure plus spécialisée comme XMouseEvent mais dont les premiers membres reprennent systématiquement ceux de la structure XEvent générique. C'est une forme propre d'implémentation objet en C mais, malheureusement, elle nous oblige quand même à nous passer des contrôles du compilateur : si on se trompe de cast, nous n'aurons aucun message d'avertissement.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    B b = a;
    b->pointeurFonction();
    Est-ce qu'on est sûr à 100% que "pointeurFonction" est à la même place en mémoire dans les deux structures et qu'il n'aurait pas été "décalé" ?
    On sait à présent que oui, mais uniquement s'il se trouve avant ton premier #ifdef, et si ces pointeurs sont bien déclarés dans le même ordre dans toutes les unités *.c (en principe depuis le même header, donc).

    Le problème est ailleurs : contrairement à la XLib, tu nommes avec le même symbole deux objets définis différemment, et c'est très mal. Si tu alloues une structure avec un malloc() dans une unité *.c et que tu renvoies le pointeur à une fonction d'une autre unité, ni l'une ni l'autre n'ont moyen de savoir a posteriori quelle est la taille du bloc allouée et si la définition n'est pas la même des deux côtés, tu vas droit à la segfault puisqu'une unité va considérer que les membres supplémentaires existent alors que ce n'est pas le cas.

    Citation Envoyé par Bktero Voir le message
    Ne serait-il pas mieux de créer une type opaque et de l'utiliser à la place de ce qui est entre la compilation conditionnelle (qu'on vire au passage) ? Ainsi, tu aurais la même structure partout, mais c'est seulement à l'endroit où le type opaque sera défini que tu pourras faire des opérations sur "l'implémentation" ?
    http://en.wikipedia.org/wiki/Opaque_pointer
    http://en.wikipedia.org/wiki/Opaque_data_type
    À ce compte-là, il est carrément préférable de se passer de pointeurs opaques et d'utiliser des handles. Soit un simple entier non signé qui contient un numéro d'objet (un OID), soit une structure générique qui donnerait un peu plus d'informations (à commencer par le type de l'objet) mais sans s'étendre.

    PS : je viens de faire un test avec des typedef avec le même alias mais pas les mêmes types réels.

    Le compilateur est d'accord. Je crois que je vais enquêter dans la norme à ce sujet ^^
    C'est-à-dire que non seulement typedef ne va pas voyager d'une unité de compilation à l'autre mais en plus, il est local à un bloc. En général, les typedef sont globaux parce qu'on les place dans les headers *.h, mais si tu mets un typedef dans la fonction main, tu as droit d'en mettre un autre portant le même nom dans une autre fonction.

    En fait, les déclarations de type avec typedef suivent exactement la même grammaire que les déclarations de variables, tant dans leur syntaxe que dans leur contexte : tu ne peux pas « redéfinir » un type déjà déclaré au sein d'un même bloc, mais tu peux déclarer un type homonyme plus local au sein d'un sous-bloc, comme avec une variable. Exemple :

    Code C : 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
    #include <stdio.h>
     
    typedef char variable;
     
    int main (void)
    {
        typedef int variable;
        variable i = 5;
        /* typedef unsigned long variable; <- Illégal ici */  
     
        if (i>0)
        {
            typedef double variable;
            variable d = 1.2;
     
            printf ("%i %f\n",i,d);
        }
     
        return 0;
    }


    Mais ne faut-il pas que les deux champs soient au "même endroit" dans les deux structures ? Si dans struct A il est en 0x04 Et dans struct B en 0x00 Faire a->foo = foo; puis b->foo(); n'appellera pas la bonne fonction non?
    Non, en effet, cela n'appellera pas la bonne fonction, puisque l'exécutable n'invoquera pas la fonction par son nom mais par « ptr+0x00000004 » et que cet offset aura été écrit en dur dans l'exécutable à la compilation.

    Citation Envoyé par Neckara Voir le message
    Les consignes nous imposent d'utiliser des pointeurs. Je n'arrive pas à lui faire comprendre que ElementColore est une forme de pointeurs (à l'image des pointeurs intelligent du C++).
    C'est parce qu'en fait, ça n'en est pas un. Ou alors il faut changer de définition mais dans ce cas, les références sont aussi pointeurs…

    À la base, les pointeurs ne sont même pas un concept propre au C : c'est une notion qui apparaît d'elle-même dès lors que l'on utilise un système d'adressage mémoire. Les smart pointers C++ sont des objets qui simulent le comportement d'un pointeur ordinaire mais qui y ajoutent des contrôles particuliers en utilisant les mécanismes de surcharge du langage.

    Citation Envoyé par Bktero Voir le message
    "Lui", c'est ton binôme ?

    Si je fais here.f();, ça ne crashe pas et appelle bien la fonction. Je ne maitrise pas trop le sujet (je ne fais pas de structures avec des pointeurs sur fonctions moi XD), pourquoi faudrait-il que TYPE soit un pointeur ?
    Sur ce point, la différence entre une fonction-membre d'un objet C++ et une structure C contenant des pointeurs de fonctions est que d'une part, le pointeur est instancié dans la structure et que, d'autre part, il est déréférencé à l'exécution. Ça signifie que « .f() » peut être différentes pour toutes les instances du type de here et que cela peut planter si le pointeur est invalide.

  13. #13
    Inactif  


    Homme Profil pro
    Doctorant sécurité informatique — Diplômé master Droit/Économie/Gestion
    Inscrit en
    Décembre 2011
    Messages
    9 026
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : France, Loire (Rhône Alpes)

    Informations professionnelles :
    Activité : Doctorant sécurité informatique — Diplômé master Droit/Économie/Gestion
    Secteur : Enseignement

    Informations forums :
    Inscription : Décembre 2011
    Messages : 9 026
    Par défaut
    Citation Envoyé par Médinoc Voir le message
    Ce qu'il vous faut, c'est ne pas pouvoir utiliser le type directement.
    Par exemple, une manière inspirée de COM, et adaptée au C où l'héritage manque:
    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
    /*IEnsembleColore.h*/
    struct IEnsembleColore;
     
    /*Ceci est mon hack qui permet d'avoir les bons pointeurs dans la vtable
      sans trop faire de casts*/
    #ifdef IMPLEMENTATION_IEnsembleColore
    #define THIS struct ImplIEnsembleColore*
    #else
    #define THIS struct IEnsembleColore*
    #endif
     
    struct IEnsembleColoreVtbl {
      TYPE * (* next)(THIS, GoshIterateur *, TYPE *);
      GoshIterateur(*createIterateur)(THIS);
      bool (*vide)(THIS);
      void (*ajouter)(THIS, TYPE);
      bool (*appartient)(THIS, TYPE);
    };
    #undef THIS
     
    typedef struct IEnsembleColore { struct IEnsembleColoreVtbl* pVtbl; } IEnsembleColore;
     
    IEnsembleColore* CreateEnsembleColore(/* paramètres */);
    Et l'implémentation:
    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
    /*ImplIEnsembleColore.c*/
     
    struct ImplIEnsembleColore;
    #define IMPLEMENTATION_IEnsembleColore
    #include "IEnsembleColore.h" 
     
    struct ImplIEnsembleColore
    {
    	IEnsembleColore iface; /*Premier membre!*/
     
    	EnsemblePosition positions;
    	Couleur couleur;
    };
     
    TYPE* IEnsembleColore_next(struct ImplIEnsembleColore* pThis, GoshIterateur * pIt, TYPE* p)
    {
    	/*...*/
    }
    bool IEnsembleColore_vide(struct ImplIEnsembleColore* pThis)
    {
    	/*...*/
    }
    void IEnsembleColore_ajouter(struct ImplIEnsembleColore* pThis, TYPE val)
    {
    	/*...*/
    }
    bool IEnsembleColore_appartient(struct ImplIEnsembleColore* pThis, TYPE val)
    {
    	/*...*/
    }
    struct IEnsembleColoreVtbl g_IEnsembleColoreVtbl = {
    	IEnsembleColore_next,
    	IEnsembleColore_vide,
    	IEnsembleColore_ajouter,
    	IEnsembleColore_appartient
    };
     
    struct IEnsembleColore* CreateEnsembleColore(/* paramètres */)
    {
    	struct ImplIEnsembleColore* pThis = malloc(sizeof *pThis);
    	/*initialisation*/
    	pThis->iface.pVtbl = g_IEnsembleColoreVtbl;
    	/*...*/
     
    	return &pThis->iface;
    }
    Ainsi, si le code client ne passe pas par CreateEnsembleColore, il n'obtient pas de pointeur sur la vtable, et ne peut donc appeler aucune fonction sur son interface invalide.
    Ce n'est pas vraiment ce qu'on recherche.
    On veut pouvoir appeler les fonctions de n'importe où.
    On veut juste empêcher l'accès aux attributs en dehors du .c.

    Citation Envoyé par Obsidian Voir le message
    Le problème est ailleurs : contrairement à la XLib, tu nommes avec le même symbole deux objets définis différemment, et c'est très mal. Si tu alloues une structure avec un malloc() dans une unité *.c et que tu renvoies le pointeur à une fonction d'une autre unité, ni l'une ni l'autre n'ont moyen de savoir a posteriori quelle est la taille du bloc allouée et si la définition n'est pas la même des deux côtés, tu vas droit à la segfault puisqu'une unité va considérer que les membres supplémentaires existent alors que ce n'est pas le cas.
    L'unité qui alloue/désalloue est la même que celle qui connait les membres supplémentaires donc il ne devrait pas y avoir de problème non?

    À ce compte-là, il est carrément préférable de se passer de pointeurs opaques et d'utiliser des handles. Soit un simple entier non signé qui contient un numéro d'objet (un OID), soit une structure générique qui donnerait un peu plus d'informations (à commencer par le type de l'objet) mais sans s'étendre.
    Je pense comprendre ce que tu veux dire mais je ne pense pas que cela respectera les consignes.

  14. #14
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 398
    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 398
    Par défaut
    Avec mon code, tu n'as pas accès aux attributs en dehors du .c.
    (vu que c'est la "vraie" structure qui les contient).
    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.

  15. #15
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Chercheur d'emploi
    Inscrit en
    Septembre 2007
    Messages
    7 504
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur d'emploi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 504
    Par défaut
    L'unité qui alloue/désalloue est la même que celle qui connait les membres supplémentaires donc il ne devrait pas y avoir de problème non?
    Les problèmes sont que :
    • Cela t'interdit l'instanciation de variables locales. Tu es obligé de passer par ta fonction d'allocation, qui va forcément la réserver dans le tas. Il faudra donc aussi penser à la libérer à chaque fois (admettons) ;
    • Par extension, cela t'interdit de faire une copie d'une instance dans une autre (en fait, ça marchera de la plus grande vers la plus petite, mais pas l'inverse) ;


    … et surtout.

    • Ça ne fonctionnera tant que tu n'essaieras pas d'indexer tes pointeurs, ni d'utiliser leur arithmétique. Cela interdit à tes fonctions allocatrices de renvoyer des tableaux de structures. Et si tu le fais quand même, le compilateur ne sera même pas en mesure de t'envoyer des messages d'avertissement.


    Ça peut être volontaire, mais il faut être sûr que ce n'est pas (comme d'habitude) une manière de justifier a posteriori un modèle insuffisamment réfléchi au départ.

    Citation Envoyé par Neckara Voir le message
    Ce n'est pas vraiment ce qu'on recherche.
    On veut pouvoir appeler les fonctions de n'importe où.
    On veut juste empêcher l'accès aux attributs en dehors du .c.
    Je vois une manière de faire cela de façon « sûre », à défaut d'être complètement propre. Tu définis deux structures. Une « publique » avec les pointeurs de fonction que tu veux laisser accessible et une « privée » avec les attributs que tu veux embarquer mais conserver confidentiels. Ensuite, tu fais une union contenant à la fois la structure privée dans les environnements sécurisés et du bourrage de la taille de la structure privée dans tous les cas :

    Code C : 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
    struct prive
    {
        int attribut1;
        int attribut2;
        int attribut3;
    };
     
    struct public
    {
      TYPE * (* next)(GoshIterateur *, struct ImplEnsembleColore *, TYPE *);
      GoshIterateur(*createIterateur)(void);
      bool (*vide)(struct ImplEnsembleColore *);
      void (*ajouter)(struct ImplEnsembleColore *, TYPE);
      bool (*appartient)(struct ImplEnsembleColore *, TYPE);
     
       union
       {
            char padding [sizeof (struct prive)];
     
    #if ENVIRONNEMENT_SECURISE
            struct prive infos;
    #endif
     
       } attributes;
    };

    De cette façon, tu garantis que la structure contiendra toujours un membre de la taille de tes attributs, mais ceux-ci ne seront plus accessibles par le compilateur en l'absence de la bonne macro à la compilation.

    C'est ce qui se fait déjà plus ou moins avec les dérivées des structures sockaddr_xxx.

  16. #16
    Inactif  


    Homme Profil pro
    Doctorant sécurité informatique — Diplômé master Droit/Économie/Gestion
    Inscrit en
    Décembre 2011
    Messages
    9 026
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : France, Loire (Rhône Alpes)

    Informations professionnelles :
    Activité : Doctorant sécurité informatique — Diplômé master Droit/Économie/Gestion
    Secteur : Enseignement

    Informations forums :
    Inscription : Décembre 2011
    Messages : 9 026
    Par défaut
    Citation Envoyé par Obsidian Voir le message
    Les problèmes sont que :
    [LIST][*]Cela t'interdit l'instanciation de variables locales. Tu es obligé de passer par ta fonction d'allocation, qui va forcément la réserver dans le tas. Il faudra donc aussi penser à la libérer à chaque fois (admettons) ;
    Je sais c'est les consignes j'y peux rien

    Citation Envoyé par Obsidian Voir le message
    Ça peut être volontaire, mais il faut être sûr que ce n'est pas (comme d'habitude) une manière de justifier a posteriori un modèle insuffisamment réfléchi au départ.
    Ne t'inquiète pas, on est parfaitement conscient que les consignes nous imposent de faire des atrocités


    Citation Envoyé par Obsidian Voir le message
    Je vois une manière de faire cela de façon « sûre », à défaut d'être complètement propre. Tu définis deux structures. Une « publique » avec les pointeurs de fonction que tu veux laisser accessible et une « privée » avec les attributs que tu veux embarquer mais conserver confidentiels. Ensuite, tu fais une union contenant à la fois la structure privée dans les environnements sécurisés et du bourrage de la taille de la structure privée dans tous les cas :

    Code C : 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
    struct prive
    {
        int attribut1;
        int attribut2;
        int attribut3;
    };
     
    struct public
    {
      TYPE * (* next)(GoshIterateur *, struct ImplEnsembleColore *, TYPE *);
      GoshIterateur(*createIterateur)(void);
      bool (*vide)(struct ImplEnsembleColore *);
      void (*ajouter)(struct ImplEnsembleColore *, TYPE);
      bool (*appartient)(struct ImplEnsembleColore *, TYPE);
     
       union
       {
            char padding [sizeof (struct prive)];
     
    #if ENVIRONNEMENT_SECURISE
            struct prive infos;
    #endif
     
       } attributes;
    };

    De cette façon, tu garantis que la structure contiendra toujours un membre de la taille de tes attributs, mais ceux-ci ne seront plus accessibles par le compilateur en l'absence de la bonne macro à la compilation.

    C'est ce qui se fait déjà plus ou moins avec les dérivées des structures sockaddr_xxx.
    C'est une très bonne idée

    Mais je crois qu'on va conserver la solution actuelle et avancer dans le projet.

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

Discussions similaires

  1. Structure de base de donnée (optimisation?)
    Par juJuv51 dans le forum SQL Procédural
    Réponses: 5
    Dernier message: 23/02/2007, 21h05
  2. Structure de base de données
    Par hphil dans le forum SQL Procédural
    Réponses: 4
    Dernier message: 19/07/2006, 20h45
  3. Réponses: 8
    Dernier message: 05/12/2005, 12h52
  4. Réponses: 4
    Dernier message: 17/02/2004, 08h36
  5. structure des bases de données Palm
    Par nomdutilisateur dans le forum Bases de données
    Réponses: 2
    Dernier message: 17/01/2004, 17h47

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