2009-11-04 5 views
14

Lors d'un examen de code, je suis venu à travers un code qui définit une structure simple comme suit:C++ des données d'alignement membres et Array Emballage

class foo { 
    unsigned char a; 
    unsigned char b; 
    unsigned char c; 
} 

Ailleurs, un tableau de ces objets est défini:

foo listOfFoos[SOME_NUM]; 

Plus tard, les structures sont copiées-premières dans un tampon:

memcpy(pBuff,listOfFoos,3*SOME_NUM); 

Ce code repose sur la assumpti ons que: a.) La taille de foo est 3, et aucun remplissage n'est appliqué, et b.) Un tableau de ces objets est emballé sans padding entre eux. Je l'ai essayé avec GNU sur deux plateformes (RedHat 64b, Solaris 9), et ça a marché sur les deux.

Les hypothèses ci-dessus sont-elles valides? Si non, dans quelles conditions (par exemple modification du système d'exploitation/compilateur) peuvent-ils échouer?

+1

Et quelqu'un a inventé std: vecteur ... –

+0

@Matthieu: Merci de nous le rappeler. Je suis sûr que le PO avait négligé cela. – nus

Répondre

16

Un tableau d'objets doit être contigu, donc il n'y a jamais de remplissage entre les objets, bien que le remplissage puisse être ajouté à la fin d'un objet (produisant presque le même effet). Étant donné que vous travaillez avec char, les hypothèses sont probablement le plus souvent correctes, mais le standard C++ ne le garantit certainement pas. Un compilateur différent, ou même simplement un changement dans les drapeaux passés à votre compilateur actuel, pourrait entraîner l'insertion de padding entre les éléments de la structure ou en suivant le dernier élément de la structure, ou les deux.

+1

Cela ne me surprendrait certainement pas si un compilateur décidait qu'il aimait les choses sur des limites de quatre octets, et mettait un octet de remplissage à la fin. –

+0

Malheureusement, la plupart ne le font pas. – Crashworks

20

Il serait certainement plus sûr de le faire:

sizeof(foo) * SOME_NUM 
+2

non seulement plus sûr, mais plus clair et se débarrasse d'un nombre magique. +1 – rmeador

+0

Oui, je suis d'accord avec ça. Je suppose que j'étais plus en train d'essayer d'obtenir l'organisation de remplissage et de tableau. Merci. –

+1

cela ne tient cependant pas compte du remplissage entre les éléments du tableau. – nschmidt

2

J'ai été sûr et remplacé le nombre magique 3 avec un sizeof(foo) je pense. Je pense que le code optimisé pour les futures architectures de processeurs introduira probablement une forme de rembourrage.

Et essayer de traquer ce genre de bug est une vraie douleur!

1

Comme d'autres l'ont dit, utiliser sizeof (foo) est un pari plus sûr. Certains compilateurs (en particulier ceux qui sont ésotériques dans le monde intégré) ajouteront un en-tête de 4 octets aux classes. D'autres peuvent faire des astuces funky d'alignement de la mémoire, en fonction des paramètres de votre compilateur.

Pour une plate-forme grand public, vous êtes probablement bien, mais ce n'est pas une garantie.

5

Si vous copiez votre tableau comme cela, vous devez utiliser

memcpy(pBuff,listOfFoos,sizeof(listOfFoos)); 

Ce sera toujours travailler aussi longtemps que vous avez alloué pBuff à la même taille. De cette façon, vous ne faites aucune hypothèse sur le rembourrage et l'alignement du tout.

La plupart des compilateurs alignent une structure ou une classe sur l'alignement requis du type le plus grand inclus. Dans votre cas de caractères, cela signifie pas d'alignement et de remplissage, mais si vous ajoutez un court exemple, votre classe serait de 6 octets de large avec un octet de remplissage ajouté entre le dernier caractère et votre court.

2

Tout se résume à l'alignement de la mémoire.Les machines 32 bits typiques lisent ou écrivent 4 octets de mémoire par tentative. Cette structure est à l'abri des problèmes car elle tombe facilement sous les 4 octets sans problème de bourrage.

Maintenant, si la structure était en tant que telle:

class foo { 
    unsigned char a; 
    unsigned char b; 
    unsigned char c; 
    unsigned int i; 
    unsigned int j; 
} 

logique Votre collègues conduirait probablement à

memcpy(pBuff,listOfFoos,11*SOME_NUM); 

(3 Char = 3 octets, 2 ints = 2 * 4 octets, donc 3 + 8)

Malheureusement, en raison du remplissage, la structure occupe en fait 12 octets. C'est parce que vous ne pouvez pas insérer trois char et un int dans ce mot de 4 octets, et il y a donc un octet d'espace rembourré qui pousse l'int dans son propre mot. Cela devient de plus en plus un problème, plus les types de données deviennent divers.

4

Je pense que la raison pour laquelle cela fonctionne parce que tous les champs de la structure sont char qui alignent un. S'il y a au moins un champ qui ne s'aligne pas 1, l'alignement de la structure/classe ne sera pas 1 (l'alignement dépendra de l'ordre des champs et de l'alignement).

Voyons voir par exemple:

#include <stdio.h> 
#include <stddef.h> 

typedef struct { 
    unsigned char a; 
    unsigned char b; 
    unsigned char c; 
} Foo; 
typedef struct { 
    unsigned short i; 
    unsigned char a; 
    unsigned char b; 
    unsigned char c; 
} Bar; 
typedef struct { Foo F[5]; } F_B; 
typedef struct { Bar B[5]; } B_F; 


#define ALIGNMENT_OF(t) offsetof(struct { char x; t test; }, test) 

int main(void) { 
    printf("Foo:: Size: %d; Alignment: %d\n", sizeof(Foo), ALIGNMENT_OF(Foo)); 
    printf("Bar:: Size: %d; Alignment: %d\n", sizeof(Bar), ALIGNMENT_OF(Bar)); 
    printf("F_B:: Size: %d; Alignment: %d\n", sizeof(F_B), ALIGNMENT_OF(F_B)); 
    printf("B_F:: Size: %d; Alignment: %d\n", sizeof(B_F), ALIGNMENT_OF(B_F)); 
} 

Lorsqu'il est exécuté, le résultat est:

Foo:: Size: 3; Alignment: 1 
Bar:: Size: 6; Alignment: 2 
F_B:: Size: 15; Alignment: 1 
B_F:: Size: 30; Alignment: 2 

Vous pouvez voir que Bar et F_B a un alignement 2, de sorte que son champ i sera correctement aligné. Vous pouvez également voir que la taille de la barre est 6 et non 5. De même, la taille de B_F (5 de Bar) est 30 et non 25. Donc, si vous avez un code dur au lieu de sizeof(...), vous aurez un problème ici.

Espérons que cela aide.

+0

semble très bien, malheureusement la structure anonyme à l'intérieur de l'appel offsetof ne compile pas en msvc 2010 – nus

2

Pour les situations où ce genre de choses est utilisé, et je ne peux pas l'éviter, j'essaie de faire une pause lorsque les présomptions ne sont plus valables. J'utilise quelque chose comme ce qui suit (ou Boost.StaticAssert si la situation le permet):

static_assert(sizeof(foo) <= 3); 

// Macro for "static-assert" (only usefull on compile-time constant expressions) 
#define static_assert(exp)   static_assert_II(exp, __LINE__) 
// Macro used by static_assert macro (don't use directly) 
#define static_assert_II(exp, line) static_assert_III(exp, line) 
// Macro used by static_assert macro (don't use directly) 
#define static_assert_III(exp, line) enum static_assertion##line{static_assert_line_##line = 1/(exp)} 
0

Il pourrait y avoir encore un problème avec sizeof() lorsque vous passez les données entre deux ordinateurs. Sur l'un d'entre eux, le code pourrait compiler avec padding et dans l'autre sans, auquel cas sizeof() donnerait des résultats différents. Si les données du tableau sont transmises d'un ordinateur à l'autre, elles seront mal interprétées car les éléments du tableau ne seront pas trouvés à l'endroit prévu. Une solution consiste à s'assurer que #pragma pack (1) est utilisé autant que possible, mais cela peut ne pas être suffisant pour les tableaux. Le mieux est de prévoir le problème et d'utiliser un remplissage à un multiple de 8 octets par élément de tableau.