3
votes

Union de structures avec uniquement des champs de bits, taille des octets de doublement de la fonction, C

Pour une raison quelconque, je n'arrive pas à comprendre que mon union de structures contenant uniquement des champs de bits met en place deux fois plus d'octets que nécessaire pour une seule structure.

#include <stdio.h>
#include <stdlib.h>

union instructionSet {
    struct Brane{
        unsigned int opcode: 4;
        unsigned int address: 12;
    } brane;
    struct Cmp{
        unsigned int opcode: 4;
        unsigned int blank: 1;
        unsigned int rsvd: 3;
        unsigned char letter: 8;
    } cmp;
    struct {
        unsigned int rsvd: 16;
    } reserved;
};

int main() {

    union instructionSet IR;// = (union instructionSet*)calloc(1, 2);

    printf("size of union %ld\n", sizeof(union instructionSet));
    printf("size of reserved %ld\n", sizeof(IR.reserved));
    printf("size of brane %ld\n", sizeof(IR.brane));
    printf("size of brane %ld\n", sizeof(IR.cmp));


    return 0;
}

Tous les appels à sizeof renvoient 4 mais à ma connaissance, ils devraient renvoyer 2.


13 commentaires

Reproduction impossible: ideone.com/NMK3hx (tous ont une taille de 4 octets).


sizeof renvoie size_t qui doit être imprimé en utilisant % zu


Le type de données dans lequel vous regroupez le champ de bits détermine la taille / l'alignement de l'entité conteneur. Donc, si un int est de 4 octets, alors int x: 1 sera compressé dans un entier aligné de 4 octets. Plus d'un champ de bits peut y être emballé, mais la taille de la structure contenante sera un multiple de 4.


@mch: La question indique qu'ils observent 4 pour tous les cas. Votre commentaire indique que vous observez 4 pour tous les cas. C'est reproduire, pas reproduire.


@TomKarzes: Selon C 2018 6.7.2.1 11, cité dans ma réponse, l'implémentation C peut choisir n'importe quelle unité de stockage qui convient; il n'est pas nécessaire de sélectionner en fonction du type.


@EricPostpischil Intéressant. Cela a dû changer à un moment donné. Il utilisé pour fonctionner comme ça, et c'est probablement toujours le cas dans la plupart des implémentations, mais je suppose que plus de flexibilité est maintenant autorisée.


Les champs de bits de caractères non signés ne sont pas du C standard, donc on ne sait pas ce que fera ce code. De plus, les champs de bits sont très mal spécifiés et doivent être évités en général.


@Lundin: Une implémentation peut définir d'autres types à autoriser. Si unsigned char n'est pas autorisé, le code viole une contrainte de 6.7.2.1 5 et serait donc obligé de produire un message de diagnostic. Donc, s'il n'y a pas de message de diagnostic, l'implémentation l'a défini comme étant autorisé, et nous pouvons dire ce que ce code fera.


Il est censé faire 4 octets ou moins, je pensais que ce serait 2 est-ce que je me trompe?


@EricPostpischil Il est répertorié parmi les extensions de langage courantes dans l'annexe J. Quoi qu'il en soit, cela n'a pas d'importance car on ne sait pas ce qu'il fera quoi qu'il en soit, sans compilateur ni système spécifique à l'esprit. Rien n'est spécifié: extrémité, bits de remplissage, octets de remplissage, alignement de l'unité de stockage, emplacement MSB, etc. Tout ce que nous savons, c'est que cela crée une sorte de blob binaire presque inutile en mémoire.


ok je devrai trouver un meilleur moyen d'implémenter ce que je dois faire car l'aspect du champ de bits est crucial, merci pour votre aide désolé pour la confusion.


Je voudrais fermer ceci maintenant car il est évident avec les réponses que j'ai besoin d'une meilleure façon de mettre en œuvre cela


@EricPostpischil la question a changé. La dernière phrase manquait et j'ai lu la première phrase car OP obtient la sortie doublée pour l'union que pour les structures.


4 Réponses :


1
votes

En savoir plus sur le remplissage de la structure de la mémoire / l'alignement de la mémoire. Par défaut, le processeur 32 bits lit la mémoire par 32 bits (4 octets) car il est plus rapide. Donc en mémoire char + uint32 sera écrit sur 4 + 4 = 8 octets (1 octet - char, 3 octets d'espace, 4 octets uint32).

Ajoutez ces lignes au début et à la fin de votre programme et le résultat sera 2. P >

struct s1 
{
    char a;
    char b;
    int c;
};

struct s2
{    
    char b;
    int c;
    char a;
};

int main() {
    printf("size of s1 %ld\n", sizeof(struct s1));
    printf("size of s2 %ld\n", sizeof(struct s2));

    return 0;
}

Voici une façon de dire au compilateur: alignez la mémoire sur 1 octet (par défaut 4 sur un processeur 32 bits).

PS: essayez cet exemple avec des #pragma pack set:

#pragma pack(1)

#pragma unpack


1 commentaires

Le truc pragma a fonctionné pour moi, merci beaucoup je vais jouer avec les autres implémentations



2
votes

C 2018 6.7.2.1 11 permet à l'implémentation C de choisir la taille du conteneur utilisée pour les champs de bits:

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 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, la question de savoir si un champ de bits qui ne rentre pas est placé dans l'unité suivante ou chevauche des unités adjacentes est définie par l'implémentation.…

L'implémentation que vous utilisez choisit apparemment d'utiliser des unités de quatre octets. C'est probablement aussi la taille d'un int dans l'implémentation, ce qui suggère qu'il s'agit d'une taille pratique pour l'implémentation.


0 commentaires

2
votes

Il y a quelques problèmes ici, tout d'abord, votre champ de bits Brane utilise un entier non signé qui est de 4 octets.

Même si vous n'utilisez que la moitié des bits, vous utilisez toujours une largeur totale de 32 bits non signée int.

Deuxièmement, vos champs de bits Cmp utilisent deux types de champs différents, vous utilisez donc 8 bits de l'int 32 bits non signé pour vos 3 premiers champs, puis vous utilisez un caractère non signé pour son entier 8 -bit. En raison des règles d'alignement des données, cette structure serait d'au moins 6 octets, mais potentiellement plus.

Si vous vouliez optimiser la taille de votre union pour ne prendre que 16 bits. Vous devez d'abord utiliser unsigned short , puis vous devez toujours utiliser le même type de champ pour tout garder dans le même espace.

Quelque chose comme ça optimiserait pleinement votre union:

union instructionSet {
    struct Brane{
        unsigned short opcode: 4;
        unsigned short address: 12;
    } brane;
    struct Cmp{
        unsigned short opcode: 4;
        unsigned short blank: 1;
        unsigned short rsvd: 3;
        unsigned short letter: 8;
    } cmp;
    struct {
        unsigned short rsvd: 16;
    } reserved;
};

Cela vous donnerait une taille de 2 tout autour.


2 commentaires

La norme C permet à une implémentation d'utiliser n'importe quelle unité de stockage dans laquelle s'insère un champ de bits; il n'est pas nécessaire d'utiliser un int de quatre octets pour un int x: 3 , ni d'utiliser un court de deux octets pour un short x: 16 . En outre, une mise en œuvre peut regrouper des champs de bits consécutifs; court x: 1; int y: 8; int z: 23; peut être compressé en 32 bits (en supposant que l'implémentation accepte short pour un champ de bits, ce qui n'est pas obligatoire). Bien que vous disiez que Cmp utilise au moins 6 octets, nous savons qu’il n’y en a que 4 dans l’implémentation d’OP.


"alors vous utilisez un caractère non signé pour son plein 8 bits" Il n'est pas spécifié par la norme la taille d'un champ binaire de caractère non signé, ou s'il diffère des champs binaires int. Même chose pour court, ça ne résout rien. "En raison des règles d'alignement des données, cette structure serait d'au moins 6 octets" Ce n'est pas vrai, car il n'est pas spécifié ce qui se passera si un champ de bits int déborde dans un char. La clé ici est de réaliser que rien de tout cela n'est standardisé ou garanti. Vous ne pouvez même pas savoir quel est le MSB dans tout cela. Vous ne pouvez pas savoir où se trouve le rembourrage. Etc.



1
votes

Il n'est pas précisé ce que fera ce code et il n'est pas significatif de le raisonner sans un système et un compilateur spécifiques à l'esprit. Les champs de bits sont simplement trop mal spécifiés dans la norme pour être utilisés de manière fiable pour des éléments tels que les dispositions de mémoire.

union instructionSet {

    /* any number of padding bits may be inserted here */ 

    /* we don't know if what will follow is MSB or LSB */

    struct Brane{
        unsigned int opcode: 4; 
        unsigned int address: 12;
    } brane;
    struct Cmp{
        unsigned int opcode: 4;
        unsigned int blank: 1;
        unsigned int rsvd: 3;
        /* anything can happen here, "letter" can merge with the previous 
           storage unit or get placed in a new storage unit */
        unsigned char letter: 8; // unsigned char does not need to be supported
    } cmp;
    struct {
        unsigned int rsvd: 16;
    } reserved;

    /* any number of padding bits may be inserted here */ 
};

La norme permet au compilateur de choisir une "unité de stockage" pour tout type de champ de bits, qui peut être de n'importe quelle taille. La norme déclare simplement:

Une implémentation peut allouer n'importe quelle unité de stockage adressable suffisamment grande pour contenir un champ de bits.

Choses que nous ne pouvons pas savoir:

  • Quelle est la taille des champs de bits de type unsigned int . 32 bits peuvent avoir du sens mais aucune garantie.
  • Si caractères non signés est autorisé pour les champs de bits.
  • Quelle est la taille des champs de bits de type caractères non signés . Pourrait être de n'importe quelle taille de 8 à 32.
  • Que se passera-t-il si le compilateur choisit une unité de stockage plus petite que les 32 bits attendus et que les bits ne rentrent pas à l'intérieur.
  • Que se passe-t-il si un champ de bits unsigned int rencontre un champ de bits unsigned char ?
  • S'il y aura un remplissage à la fin de l'union ou au début (alignement).
  • Comment les unités de stockage individuelles au sein des structures sont alignées
  • L'emplacement du MSB.

Ce que nous pouvons savoir:

  • Nous avons créé une sorte de blob binaire en mémoire.
  • Le premier octet de l'objet blob réside sur l'adresse la moins significative en mémoire. Il peut contenir des données ou un remplissage.

Des connaissances supplémentaires peuvent être obtenues en ayant un système et un compilateur très spécifiques à l'esprit.


Au lieu des champs de bits, nous pouvons utiliser des opérations binaires 100% portables et déterministes, qui donnent de toute façon le même code machine.


1 commentaires

Je peux finir par faire cela car il y a beaucoup de champs qui se chevauchent et je pourrais avoir une fonction pour masquer arbitrairement au besoin.