web-dev-qa-db-fra.com

C / C ++: forcer l'ordre et l'alignement des champs de bits

J'ai lu que l'ordre des champs de bits dans une structure est spécifique à la plate-forme. Qu'en est-il si j'utilise différentes options d'emballage spécifiques au compilateur, ces données de garantie seront-elles stockées dans le bon ordre au fur et à mesure qu'elles sont écrites? Par exemple:

struct Message
{
  unsigned int version : 3;
  unsigned int type : 1;
  unsigned int id : 5;
  unsigned int data : 6;
} __attribute__ ((__packed__));

Sur un processeur Intel avec le compilateur GCC, les champs ont été disposés en mémoire comme ils sont affichés. Message.version était les 3 premiers bits du tampon et Message.type a suivi. Si je trouve des options d'emballage de structure équivalentes pour divers compilateurs, cela sera-t-il multiplateforme?

80
dewald

Non, il ne sera pas entièrement portable. Les options d'emballage pour les structures sont des extensions et elles-mêmes ne sont pas entièrement portables. En plus de cela, C99 §6.7.2.1, paragraphe 10 dit: "L'ordre d'allocation des champs binaires au sein d'une unité (de haut en bas ou de bas en haut) est défini par l'implémentation."

Même un seul compilateur peut disposer le champ de bits différemment en fonction de l'endianité de la plate-forme cible, par exemple.

95
Stephen Canon

Les champs de bits varient considérablement d'un compilateur à l'autre, désolé.

Avec GCC, les machines big endian présentent les bits big end en premier et les petites machines endian présentent les bits little end en premier.

K&R dit que "les membres des champs adjacents [bit-] des structures sont regroupés dans des unités de stockage dépendant de l'implémentation dans une direction dépendante de l'implémentation. Lorsqu'un champ suivant un autre champ ne conviendra pas ... il peut être divisé en unités ou l'unité peut être un champ sans nom de largeur 0 force ce remplissage ... "

Par conséquent, si vous avez besoin d'une disposition binaire indépendante de la machine, vous devez le faire vous-même.

Cette dernière déclaration s'applique également aux non-champs de bits en raison du remplissage - cependant tous les compilateurs semblent avoir un moyen de forcer le remplissage d'octets d'une structure, comme je vois que vous avez déjà découvert pour GCC.

42
Joshua

Les champs de bits doivent être évités - ils ne sont pas très portables entre les compilateurs, même pour la même plate-forme. de la norme C99 6.7.2.1/10 - "Structure and union specifiers" (il existe une formulation similaire dans la norme C90):

Une implémentation peut allouer n'importe quelle unité de stockage adressable suffisamment grande pour contenir un champ de bits. S'il reste suffisamment d'espace, un champ binaire qui suit immédiatement un autre champ binaire dans une structure doit être compressé en bits adjacents de la même unité. S'il reste un espace insuffisant, si un champ binaire qui ne correspond pas est placé dans l'unité suivante ou chevauche des unités adjacentes est défini par l'implémentation. L'ordre d'allocation des champs binaires au sein d'une unité (de haut en bas ou de bas en haut) est défini par l'implémentation. L'alignement de l'unité de stockage adressable n'est pas spécifié.

Vous ne pouvez pas garantir si un champ de bits s'étendra sur une frontière int ou non et vous ne pouvez pas spécifier si un champ de bits commence à l'extrémité inférieure de l'int ou à l'extrémité supérieure de l'int (cela est indépendant du fait que le processeur soit ou non big-endian ou little-endian).

Préférez les masques de bit. Utilisez des inlines (ou même des macros) pour définir, effacer et tester les bits.

33
Michael Burr

l'endianisme parle d'ordres d'octets et non d'ordres de bits. De nos jours, il est sûr à 99% que les ordres de bits sont fixes. Cependant, lors de l'utilisation de champs binaires, l'endianité doit être prise en compte. Voir l'exemple ci-dessous.

#include <stdio.h>

typedef struct tagT{

    int a:4;
    int b:4;
    int c:8;
    int d:16;
}T;


int main()
{
    char data[]={0x12,0x34,0x56,0x78};
    T *t = (T*)data;
    printf("a =0x%x\n" ,t->a);
    printf("b =0x%x\n" ,t->b);
    printf("c =0x%x\n" ,t->c);
    printf("d =0x%x\n" ,t->d);

    return 0;
}

//- big endian :  mips24k-linux-gcc (GCC) 4.2.3 - big endian
a =0x1
b =0x2
c =0x34
d =0x5678
 1   2   3   4   5   6   7   8
\_/ \_/ \_____/ \_____________/
 a   b     c           d

// - little endian : gcc (Ubuntu 4.3.2-1ubuntu11) 4.3.2
a =0x2
b =0x1
c =0x34
d =0x7856
 7   8   5   6   3   4   1   2
\_____________/ \_____/ \_/ \_/
       d           c     b   a
9
pierrotlefou

La plupart du temps, probablement, mais ne pariez pas sur la ferme, car si vous vous trompez, vous perdrez gros.

Si vous avez vraiment, vraiment besoin d'avoir des informations binaires identiques, vous devrez créer des champs de bits avec des masques de bits - par exemple vous utilisez un court non signé (16 bits) pour Message, puis créez des éléments comme versionMask = 0xE000 pour représenter les trois bits supérieurs.

Il y a un problème similaire avec l'alignement dans les structures. Par exemple, les CPU Sparc, PowerPC et 680x0 sont tous de grande taille, et la valeur par défaut courante pour les compilateurs Sparc et PowerPC est d'aligner les membres de structure sur des limites de 4 octets. Cependant, un compilateur que j'ai utilisé pour 680x0 n'était aligné que sur des limites de 2 octets - et il n'y avait pas d'option pour changer l'alignement!

Ainsi, pour certaines structures, les tailles sur Sparc et PowerPC sont identiques, mais plus petites sur 680x0, et certains des membres sont dans des décalages de mémoire différents dans la structure.

C'était un problème avec un projet sur lequel j'ai travaillé, car un processus serveur s'exécutant sur Sparc interrogerait un client et découvrirait qu'il était big-endian, et supposerait qu'il pourrait simplement injecter des structures binaires sur le réseau et que le client pourrait faire face. Et cela a bien fonctionné sur les clients PowerPC et s'est écrasé sur les clients 680x0. Je n'ai pas écrit le code et il a fallu un certain temps pour trouver le problème. Mais c'était facile à réparer une fois que je l'ai fait.

6
Bob Murphy