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 :

optimisation : malloc et alignement de structures


Sujet :

C

  1. #1
    Membre éprouvé
    Profil pro
    Inscrit en
    Septembre 2009
    Messages
    1 821
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2009
    Messages : 1 821
    Points : 979
    Points
    979
    Par défaut optimisation : malloc et alignement de structures
    Bonjour,

    Je veux réserver via malloc un espace mémoire contenant un tableau de type STRUCT_FOO (une structure quelconque).

    Est-ce que ce code fonctionne forcement ? (pour simplifier le code, on admettra que malloc ne renvoit pas NULL)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    const int nb_elements = 10;
    STRUCT_FOO * tab = malloc(sizeof(STRUCT_FOO)*nb_elements);
    tab[5].prop1 = 5;
    tab[10].prop2 = 10;
    ...
    => Entre deux éléments de mon tableau, il ne faut pas du padding ? comment le calculer/gérer ?
    => L'adresse du premier élément du tableau ne doit pas être aligné sur une adresse divisible par la taille de STRUCT_FOO ? Comment gérer ce problème ?

    Merci d'avance,

  2. #2
    Expert confirmé
    Inscrit en
    Mars 2005
    Messages
    1 431
    Détails du profil
    Informations forums :
    Inscription : Mars 2005
    Messages : 1 431
    Points : 4 182
    Points
    4 182
    Par défaut
    Toutes les contraintes d'alignement / offsets sont automatiquement prises en charge, c'est un avantage du C par rapport à l'assembleur. Par curiosité, pourquoi pensais-tu que tu devais te préoccuper de ce genre de chose ?

    Si pour d'obscures raisons techniques tu dois toi-même spécifier et / ou contrôler l'alignement de tes champs, des directives plus ou moins standards selon la norme utilisée existent pour ton compilateur.

    En revanche ce code plantera pour une autre raison qui est un dépassement de capacité.

  3. #3
    Membre éprouvé
    Profil pro
    Inscrit en
    Septembre 2009
    Messages
    1 821
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2009
    Messages : 1 821
    Points : 979
    Points
    979
    Par défaut
    Citation Envoyé par Matt_Houston Voir le message
    Par curiosité, pourquoi pensais-tu que tu devais te préoccuper de ce genre de chose ?
    Parce que par le passé, j'ai déja eu des problèmes de gestion de pointeurs à cause de problèmes d'alignement... malheureusement, je ne me rappelle plus exactement du problème : ma mémoire flanche

  4. #4
    Expert confirmé
    Inscrit en
    Mars 2005
    Messages
    1 431
    Détails du profil
    Informations forums :
    Inscription : Mars 2005
    Messages : 1 431
    Points : 4 182
    Points
    4 182
    Par défaut
    Tu devras t'en préoccuper si tu comptes interpréter manuellement ta structure sous forme de char *, mais si tu t'en tiens aux champs définis c'est transparent.

  5. #5
    Membre éprouvé
    Profil pro
    Inscrit en
    Septembre 2009
    Messages
    1 821
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2009
    Messages : 1 821
    Points : 979
    Points
    979
    Par défaut
    ok merci

  6. #6
    Expert éminent
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Décembre 2015
    Messages
    1 562
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

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

    Informations forums :
    Inscription : Décembre 2015
    Messages : 1 562
    Points : 7 628
    Points
    7 628
    Par défaut
    Bonjour,

    Plus précisément c'est sizeof() qui gère le padding, et malloc() retourne toujours une donnée alignée sur le plus petit entre
    * l'alignement maximum (typiquement 8 ou 16)
    * et la taille requise tronquée en puissance de 2.
    On a ainsi un resultat qui fonctionnera dans tous les cas.
    Exemple en 32bits
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    struct A {
       short x;
       char y;
    };
    sizeof(A) //=> 4=2+2 qui est multiple du plus grand alignement, ici celui du short vaut 2
    A* p = malloc( 5 * sizeof(A) ); // => un pointeur multiple de 16 avec réservation de 4*5=20 octets
    A* q = malloc( 3 * sizeof(A) ); // => un pointeur multiple de 8 avec réservation de 4*3=12 octets
    p[1] // est à l'offset 1*8=8

  7. #7
    Membre éprouvé
    Profil pro
    Inscrit en
    Septembre 2009
    Messages
    1 821
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2009
    Messages : 1 821
    Points : 979
    Points
    979
    Par défaut
    ok merci pour ces précisions

  8. #8
    Membre éprouvé
    Profil pro
    Inscrit en
    Septembre 2009
    Messages
    1 821
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2009
    Messages : 1 821
    Points : 979
    Points
    979
    Par défaut
    Par contre si dans ma zone mémoire, je cascade deux tableaux de type différent, je ne risque pas d'avoir de problème d'alignement (ex: si STRUCT_FOO1 plus petit que STRUCT_FOO2) ?

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    const int nb_elements = 10;
    void * ptr = malloc((sizeof(STRUCT_FOO1) + sizeof(STRUCT_FOO2))*nb_elements);
     
    STRUCT_FOO1 * tab1 = ptr;
    STRUCT_FOO2 * tab2 = ptr  + (sizeof(STRUCT_FOO1) * nb_elements);
    Comment géré ce problème de manière générique (que si je change la taille d'une des structure, que mon code fonctionne quand même) ?

  9. #9
    Expert confirmé
    Inscrit en
    Mars 2005
    Messages
    1 431
    Détails du profil
    Informations forums :
    Inscription : Mars 2005
    Messages : 1 431
    Points : 4 182
    Points
    4 182
    Par défaut
    Je ne sais pas ce que tu entends par « cascader », ce que tu cherches à obtenir en mémoire (un schéma peut-être ?).

    Si tu veux « mélanger » les deux éléments au sein d'un seul tableau, alors il faut utiliser une union :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    typedef union {
        STRUCT_FOO1 foo1;
        STRUCT_FOO2 foo2;
    } UNION_BAR;
     
    STRUCT_FOO1 myfoo1;
    STRUCT_FOO2 myfoo2;
    // ...
    UNION_BAR *tbar = malloc(10 * sizeof(UNION_BAR));
    tbar[2].foo1 = myfoo1;
    tbar[6].foo2 = myfoo2;
    Bien évidemment sizeof(UNION_BAR) vaut la taille du plus « gros » membre de l'union. Les éléments d'un tableau ou assimilé ont toujours tous la même taille.

    À noter qu'en général on recourt à un identifiant afin de savoir quel membre de l'union est initialisé, soit interne (un dans chaque membre), soit externe comme 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
    typedef enum {
        BAR_FOO1,
        BAR_FOO2
    } BARTYPE;
     
    typedef struct {
        union {
            STRUCT_FOO1 foo1;
            STRUCT_FOO2 foo2;
        }; // membre anonyme : requiert au moins C99
     
        BARTYPE type;
    } STRUCT_BAR;
     
    STRUCT_BAR bar;
    // ...
    switch (bar.type) {
        case BAR_FOO1:
            return bar.foo1;
        case BAR_FOO2:
            return bar.foo2;
    }

  10. #10
    Membre éprouvé
    Profil pro
    Inscrit en
    Septembre 2009
    Messages
    1 821
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2009
    Messages : 1 821
    Points : 979
    Points
    979
    Par défaut
    Par cascader, je voulais dire que dans la zone mémoire réservée, je place au début le premier tableau et ensuite le second (ce n'est pas une union)
    => en faite le plus simple je pense est de créer une nouvelle structure qui contient le deux tableaux, non ?

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    typedef struct {
       STRUCT_FOO1 tab1[10];
       STRUCT_FOO2 tab2[20];
    } STRUCT_MERGE;
     
     
    STRUCT_MERGE * ptr = malloc(sizeof(STRUCT_MERGE));
     
    STRUCT_FOO1 * tab1 = ptr->tab1;
    STRUCT_FOO2 * tab2 = ptr->tab2;
    ... mais cette solution fonctionne que si on connait à l'avance le nombre d'éléments des tableaux

  11. #11
    Expert confirmé
    Inscrit en
    Mars 2005
    Messages
    1 431
    Détails du profil
    Informations forums :
    Inscription : Mars 2005
    Messages : 1 431
    Points : 4 182
    Points
    4 182
    Par défaut
    C'est-à-dire interlacer les structures en contigü comme ceci : 12121212121212... ? Oui la solution est de définir un enregistrement composite.

  12. #12
    Membre éprouvé
    Profil pro
    Inscrit en
    Septembre 2009
    Messages
    1 821
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2009
    Messages : 1 821
    Points : 979
    Points
    979
    Par défaut
    je n'ai pas compris ton message
    j'ai complété mon précédent message peut-être que tu comprendras mieux

  13. #13
    Expert confirmé
    Inscrit en
    Mars 2005
    Messages
    1 431
    Détails du profil
    Informations forums :
    Inscription : Mars 2005
    Messages : 1 431
    Points : 4 182
    Points
    4 182
    Par défaut
    C'est pour cela que je recommandais un schéma qui vaut souvent tous les discours.

    Ok je pense avoir enfin saisi, tu veux deux tableaux indépendants mais contigüs c'est cela (alloués d'un bloc) ? Je pense que mélanger les deux types c'est s'exposer à un comportement indéfini.

  14. #14
    Membre éprouvé
    Profil pro
    Inscrit en
    Septembre 2009
    Messages
    1 821
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2009
    Messages : 1 821
    Points : 979
    Points
    979
    Par défaut
    NOn ça ne fonctionne pas

    code de test :
    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
     
    #include <stdio.h>
    #include <stdlib.h>
     
    typedef struct{
        char a;
        char b;
        char c;
    } STRUCT_FOO1;
     
    typedef struct{
        int a;
        int b;
    } STRUCT_FOO2;
     
    int main()
    {
        printf("sizeof(STRUCT_FOO1) : %i\n", sizeof(STRUCT_FOO1));
        printf("sizeof(STRUCT_FOO2) : %i\n", sizeof(STRUCT_FOO2));
     
        char *ptr = malloc(3 * (sizeof(STRUCT_FOO1) + sizeof(STRUCT_FOO2)));
        printf("ptr : %p\n", ptr);
     
        STRUCT_FOO1 *tab1 = (STRUCT_FOO1 *)ptr;
        STRUCT_FOO2 *tab2 = (STRUCT_FOO2 *)(ptr + 3 * sizeof(STRUCT_FOO1));
        printf("tab1 : %p\n", tab1);
        printf("tab2 : %p\n", tab2);
     
        return 0;
    }
    => tab2 pointe sur une adresse impaire : sur un PC, ce type d'erreur ne semble pas trop poser de problème mais sur un µControlleur, c'est certains que ça pose problème car un int (4 octets sur architecture 32bits) ne peut pas être stocké sur une adresse impaire.

  15. #15
    Membre éprouvé
    Profil pro
    Inscrit en
    Septembre 2009
    Messages
    1 821
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2009
    Messages : 1 821
    Points : 979
    Points
    979
    Par défaut
    En encapsulant tout dans une structure, l'alignement devient correcte :
    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
    #include <stdio.h>
    #include <stdlib.h>
     
    typedef struct{
        char a;
        char b;
        char c;
    } STRUCT_FOO1;
     
    typedef struct{
        int a;
        int b;
    } STRUCT_FOO2;
     
    typedef struct{
        STRUCT_FOO1 tab1[3];
        STRUCT_FOO2 tab2[1];
    } STRUCT_MERGE;
     
     
    int main()
    {
        printf("sizeof(STRUCT_FOO1) : %i\n", sizeof(STRUCT_FOO1));
        printf("sizeof(STRUCT_FOO2) : %i\n", sizeof(STRUCT_FOO2));
     
        char *ptr = malloc(sizeof(STRUCT_MERGE));
        printf("ptr : %p\n", ptr);
     
        STRUCT_MERGE * struct1 = (STRUCT_MERGE *)ptr;
        STRUCT_FOO1 *tab1 = struct1->tab1;
        STRUCT_FOO2 *tab2 = struct1->tab2;
        printf("tab1 : %p\n", tab1);
        printf("tab2 : %p\n", tab2);
     
        return 0;
    }
    ... mais on ne peut pas utiliser cette méthode pour gérer des tableaux a taille variable : comment faire (en utilisant qu'un seul malloc) ?

  16. #16
    Expert confirmé
    Inscrit en
    Mars 2005
    Messages
    1 431
    Détails du profil
    Informations forums :
    Inscription : Mars 2005
    Messages : 1 431
    Points : 4 182
    Points
    4 182
    Par défaut
    Tu as raison, je suis probablement fatigué... j'ai édité mon précédent message pour ne pas induire des lecteurs en erreur. Je pense qu'il est impossible de mélanger les types sans entraîner un comportement indéfini (je ne parviens pas à trouver un extrait de la norme là qui l'exprime noir sur blanc). Je veux dire, ça fonctionnera probablement sur beaucoup de plate-formes, jusqu'au jour où ça craquera.

    Pourquoi ne veux-tu faire qu'un seul malloc ?

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

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

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 518
    Points
    41 518
    Par défaut
    On peut mettre deux tableaux dans un seul malloc(), mais il faut faire gaffe lors du calcul de la taille totale et de l'offset du second tableau, aux contraintes d'alignement.

    Par chance, on peut aller "du côté de la sécurité" en utilisant la taille de la structure comme alignement (vu que sizeof() doit inclure l'alignement, il ne retournera jamais une valeur fausse). On aurait donc un truc du genre:
    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
    size_t tailleTotale = 0;
    size_t offset1 = 0, offset2 = 0;
     
    /*Le tableau de Struct1*/
    tailleTotale = arrondirAuMultipleSuperieur(tailleTotale, sizeof(Struct1));
    offset1 = tailleTotale;
    tailleTotale += numberOfStruct1 * sizeof(Struct1);
     
    /*Le tableau de Struct2*/
    tailleTotale = arrondirAuMultipleSuperieur(tailleTotale, sizeof(Struct2));
    offset2 = tailleTotale;
    tailleTotale += numberOfStruct2 * sizeof(Struct2);
     
    /*etc.*/
     
    /*Avec la fonction définie du genre:*/
    size_t arrondirAuMultipleSuperieur(size_t valeur, size_t tailleBloc)
    {
    	valeur += tailleBloc - 1;
    	return (valeur / tailleBloc) * tailleBloc;
    }
    Et on peut même décider de se faire une structure d'en-tête contenant les pointeurs vers les tableaux voulus:
    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
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    size_t tailleTotale = 0;
    size_t offset1 = 0, offset2 = 0;
     
    tailleTotale += sizeof(StructEnTete);
     
    /*Le tableau de Struct1*/
    tailleTotale = arrondirAuMultipleSuperieur(tailleTotale, sizeof(Struct1));
    offset1 = tailleTotale;
    tailleTotale += numberOfStruct1 * sizeof(Struct1);
     
    /*Le tableau de Struct2*/
    tailleTotale = arrondirAuMultipleSuperieur(tailleTotale, sizeof(Struct2));
    offset2 = tailleTotale;
    tailleTotale += numberOfStruct2 * sizeof(Struct2);
     
    /*etc.*/
     
    void* buf = malloc(tailleTotale); /*C'est du C, pas besoin de cast explicite pour void* */
    StructEnTete* pEnTete = buf;
    pEnTete->pTableau1 = ajoutPointeurOffset(buf, offset1);
    pEnTete->pTableau2 = ajoutPointeurOffset(buf, offset2);
    /*etc.*/
     
    /*Avec la fonction définie du genre:*/
    void* ajoutPointeurOffset(void* pv, size_t offset)
    {
    	intptr_t valeur = (intptr_t)pv;
    	valeur += offset;
    	return (void*)valeur;
    }
    void const* ajoutPointeurOffsetC(void const* pcv, size_t offset)
    {
    	intptr_t valeur = (intptr_t)pv;
    	valeur += offset;
    	return (void const*)valeur;
    }
    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.

  18. #18
    Membre éprouvé
    Profil pro
    Inscrit en
    Septembre 2009
    Messages
    1 821
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2009
    Messages : 1 821
    Points : 979
    Points
    979
    Par défaut
    Citation Envoyé par Matt_Houston Voir le message
    Pourquoi ne veux-tu faire qu'un seul malloc ?
    Parce que sur des systèmes embarqués où la taille de la RAM est très limitée, si tu fais pleins de réservations mémoire pour de petites structures (ex: listes chainées), le gestionnaire de mémoire dynamique va devoir stocker toutes les adresses mémoires réservées et leur taille : ce qui va consommer de la RAM de manière non négligeable.
    Actuellement, je n'ai pas de problème de taille de RAM mais autant utiliser les "bonnes pratiques" dès maintenant

  19. #19
    Membre éprouvé
    Profil pro
    Inscrit en
    Septembre 2009
    Messages
    1 821
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2009
    Messages : 1 821
    Points : 979
    Points
    979
    Par défaut
    Citation Envoyé par Médinoc Voir le message
    Par chance, on peut aller "du côté de la sécurité" en utilisant la taille de la structure comme alignement (vu que sizeof() doit inclure l'alignement, il ne retournera jamais une valeur fausse). On aurait donc un truc du genre:
    Merci pour ta réponse
    Il me semble que ce n'est pas forcément optimal car d'après les tests que j'ai fait, l'alignement d'une structure ne se fait pas sur sa taille totale mais sur la taille du type le plus gros utilisé dans la structure (à confirmer avec la norme que je ne connais pas bien).
    Aussi, je pense que l'alignement est opérationnel si on utilise des adresses divisibles par la taille d'un "void *" : à confirmer.

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

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

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 518
    Points
    41 518
    Par défaut
    Je n'ai pas dit que c'était optimal, j'ai dit que c'était "play it safe".
    Si tu connais l'alignement des structures sur ta plate-forme (ou si ton compilo supporte alignof), tu peux faire quelque chose d'optimal au lieu de quelque chose de "safe".
    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.

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 2 12 DernièreDernière

Discussions similaires

  1. Malloc de tableau dans structure.
    Par Mornor dans le forum C
    Réponses: 2
    Dernier message: 01/05/2013, 17h14
  2. Réponses: 6
    Dernier message: 15/12/2006, 14h55
  3. alignement de structure
    Par dus dans le forum MFC
    Réponses: 3
    Dernier message: 23/06/2006, 19h21
  4. alignement parasite dans une structure !
    Par - Robby - dans le forum C
    Réponses: 4
    Dernier message: 24/03/2006, 00h02

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