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

87

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 étaient disposés en mémoire tels qu'ils sont affichés. Message.versionétait les 3 premiers bits du tampon, et Message.typesuivit. Si je trouve des options de compression de structure équivalentes pour divers compilateurs, est-ce que ce sera multiplateforme?

Dewald
la source
17
Puisqu'un tampon est un ensemble d'octets et non de bits, "les 3 premiers bits du tampon" n'est pas un concept précis. Considérez-vous les 3 bits de poids faible du premier octet comme les 3 premiers bits ou les 3 bits de poids fort?
caf
2
Lors du transit sur le réseau, "Les 3 premiers bits du buffer" s'avère très bien défini.
Joshua
2
@Joshua IIRC, Ethernet transmet le bit le moins significatif de chaque octet premier ( ce qui est la raison pour laquelle le bit de diffusion est l' endroit où il est).
tc.
Quand vous dites «portable» et «multiplateforme», que voulez-vous dire? L'exécutable accédera correctement à la commande quel que soit le système d'exploitation cible - ou - le code se compilera indépendamment de la chaîne d'outils?
Garet Claborn

Réponses:

103

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

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

Stephen Canon
la source
Oui, le GCC, par exemple, note spécifiquement que les champs de bits sont organisés selon l'ABI, pas l'implémentation. Donc, rester sur un seul compilateur n'est pas suffisant pour garantir l'ordre. L'architecture doit également être vérifiée. Un peu un cauchemar pour la portabilité, vraiment.
underscore_d
10
Pourquoi la norme C n'a-t-elle pas garanti un ordre pour les champs de bits?
Aaron Campbell
7
Il est difficile de définir de manière cohérente et portative «l'ordre» des bits dans les octets, encore moins l'ordre des bits qui peuvent traverser les limites des octets. Toute définition que vous choisirez ne correspondra pas à une quantité considérable de pratiques existantes.
Stephen Canon
2
La définition de l'implémentation permet une optimisation spécifique à la plateforme. Sur certaines plates-formes, le remplissage entre les champs de bits peut améliorer l'accès, imaginez quatre champs de sept bits dans un int 32 bits: les aligner tous les 8 bits est une amélioration significative pour les plates-formes qui ont des lectures d'octets.
peterchen
packedn'appliquer la commande: stackoverflow.com/questions/1756811/... comment faire respecter l' ordre des bits: stackoverflow.com/questions/6728218/gcc-compiler-bit-order
Ciro Santilli郝海东冠状病六四事件法轮功
45

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

Avec GCC, les machines big endian placent les bits big end en premier et les machines little endian disposent les bits little end first.

K&R dit: "Les membres de champ [bits] adjacents des structures sont emballés dans des unités de stockage dépendant de l'implémentation dans une direction dépendant de l'implémentation. Lorsqu'un champ suivant un autre champ ne rentre pas ... il peut être divisé entre les unités ou l'unité peut être rembourré. 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 champs non binaires en raison du remplissage - cependant, tous les compilateurs semblent avoir un moyen de forcer le compactage d'octets d'une structure, comme je vois que vous l'avez déjà découvert pour GCC.

Joshua
la source
Est-ce que K&R est vraiment considéré comme une référence utile, étant donné qu'il s'agissait d'une pré-normalisation et a (je suppose?) Probablement été remplacé dans de nombreux domaines?
underscore_d
1
Mon K&R est post-ANSI.
Joshua
1
Maintenant, c'est embarrassant: je ne savais pas qu'ils avaient publié une révision post-ANSI. Ma faute!
underscore_d
35

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 - "Spécificateurs de structure et d'union" (il existe une formulation similaire dans la norme C90):

Une mise en œuvre peut allouer toute unité de stockage adressable suffisamment grande pour contenir un champ de bits. S'il reste suffisamment d'espace, un champ de bits qui suit immédiatement un autre champ de bits dans une structure doit être emballé dans des bits adjacents de la même unité. S'il reste un espace insuffisant, le fait qu'un champ de bits qui ne rentre pas soit placé dans l'unité suivante ou chevauche des unités adjacentes est défini par l'implémentation. L'ordre d'attribution des champs de bits au sein d'une unité (d'ordre élevé à bas ou d'ordre bas à 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 `` couvrira '' une limite 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 est big-endian ou little-endian).

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

Michael Burr
la source
2
L'ordre des champs de bits peut être déterminé au moment de la compilation.
Greg A. Woods
9
De plus, les champs de bits sont hautement préférés lorsqu'il s'agit d'indicateurs de bits qui n'ont pas de représentation externe en dehors du programme (c'est-à-dire sur disque ou dans des registres ou en mémoire accédés par d'autres programmes, etc.).
Greg A. Woods
1
@ GregA.Woods: Si tel est vraiment le cas, veuillez fournir une réponse décrivant comment. Je n'ai rien trouvé d'autre que votre commentaire lors de la recherche sur Google ...
mozzbozz
1
@ GregA.Woods: Désolé, j'aurais dû écrire à quel commentaire je faisais référence. Je voulais dire: vous dites que "l'ordre des champs de bits peut être déterminé au moment de la compilation.". Je ne peux rien à ce sujet et comment le faire.
mozzbozz
2
@mozzbozz Jetez un œil à planix.com/~woods/projects/wsg2000.c et recherchez les définitions et l'utilisation de _BIT_FIELDS_LTOHet_BIT_FIELDS_HTOL
Greg A. Woods
11

endianness 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 corrigés. Cependant, lors de l'utilisation de champs de bits, l'endianness doit être pris 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
Pierrotlefou
la source
6
La sortie de a et b indique que l'endianness parle toujours d'ordres de bits ET d'ordres d'octets.
Programmeur Windows
exemple merveilleux avec des problèmes d'ordre des bits et de l'ordre des octets
Jonathan
1
Avez-vous réellement compilé et exécuté le code? Les valeurs pour "a" et "b" ne me semblent pas logiques: vous dites en gros que le compilateur échangera les nibbles dans un octet à cause de l'endianité. Dans le cas de "d", les endiannes ne devraient pas affecter l'ordre des octets dans les tableaux de caractères (en supposant que char a une longueur de 1 octet); si le compilateur faisait cela, nous ne pourrions pas parcourir un tableau en utilisant des pointeurs. Si, au contraire, vous aviez utilisé un tableau de deux entiers 16 bits par exemple: uint16 data [] = {0x1234,0x5678}; alors d serait certainement 0x7856 dans les systèmes little endian.
Krauss le
6

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

Si vous avez vraiment, vraiment besoin d'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 faites des choses comme versionMask = 0xE000 pour représenter les trois bits les plus hauts.

Il y a un problème similaire avec l'alignement dans les structures. Par exemple, les processeurs Sparc, PowerPC et 680x0 sont tous big-endian, et la valeur par défaut commune 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 uniquement aligné sur des limites de 2 octets - et il n'y avait aucune 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 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 interrogeait un client et découvrait qu'il était big-endian, et supposait qu'il pouvait simplement éjecter des structures binaires sur le réseau et que le client pouvait faire face. Et cela a bien fonctionné sur les clients PowerPC et a planté énormément 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.

Bob Murphy
la source
1

Merci @BenVoigt pour votre commentaire très utile commençant

Non, ils ont été créés pour économiser de la mémoire.

Source de Linux n'utiliser un champ de bits pour correspondre à une structure externe: /usr/include/linux/ip.h a ce code pour le premier octet d'un datagramme IP

struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
        __u8    ihl:4,
                version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
        __u8    version:4,
                ihl:4;
#else
#error  "Please fix <asm/byteorder.h>"
#endif

Cependant, à la lumière de votre commentaire, je renonce à essayer de faire fonctionner cela pour le champ de bits multi-octets frag_off .

Duncan Roe
la source
-9

Bien sûr, la meilleure réponse est d'utiliser une classe qui lit / écrit les champs de bits sous forme de flux. L'utilisation de la structure de champ de bits C n'est tout simplement pas garantie. Sans oublier qu'il est considéré comme non professionnel / paresseux / stupide d'utiliser cela dans le codage du monde réel.

99999999
la source
5
Je pense qu'il est faux de dire qu'il est stupide d'utiliser des champs de bits car cela fournit un moyen très propre de représenter les registres matériels, qu'il a été créé pour modéliser, en C.
trondd
13
@trondd: Non, ils ont été créés pour économiser de la mémoire. Les champs de bits ne sont pas destinés à être mappés à des structures de données extérieures, telles que les registres matériels mappés en mémoire, les protocoles réseau ou les formats de fichiers. S'ils étaient destinés à être mappés à des structures de données extérieures, l'ordre d'emballage aurait été normalisé.
Ben Voigt
2
L'utilisation de bits économise de la mémoire. L'utilisation de champs de bits augmente la lisibilité. Utiliser moins de mémoire est plus rapide. L'utilisation de bits permet des opérations atomiques plus complexes. Dans les applications du monde réel, il y a besoin de performances et d'opérations atomiques complexes. Cette réponse ne fonctionnerait pas pour nous.
johnnycrash
@BenVoigt probablement vrai, mais si un programmeur est prêt à confirmer que la commande de son compilateur / ABI correspond à ce dont il a besoin, et sacrifie la portabilité rapide en conséquence - alors il peut certainement remplir ce rôle. Quant à 9 *, quelle masse d'autorité de "codeurs du monde réel" considère que toute utilisation de champs de bits est "non professionnelle / paresseuse / stupide" et où l'ont-ils déclaré?
underscore_d
2
Utiliser moins de mémoire n'est pas toujours plus rapide; il est souvent plus efficace d'utiliser plus de mémoire et de réduire les opérations de post-lecture, et le mode processeur / processeur peut rendre cela encore plus vrai.
Dave Newton du