1
votes

Comment créer un entier non signé 24 bits en C

Je travaille sur une application embarquée où la RAM est extrêmement restreinte. Pour cela, je dois créer un type de données entier non signé 24 bits. Je fais cela en utilisant une structure:

typedef struct
{
    uint8_t blah[3];
} mytype;

Cependant, lorsque j'interroge la taille d'une variable de ce type, elle renvoie "4", c'est-à-dire:

    uint24_t x;
    x.v = 0;
    printf("Size = %u", sizeof(x));


4 commentaires

Je ne suis pas sûr que cela vous fera économiser de la RAM. Le processeur est très probablement 32 bits et donc chaque adresse d'une variable sera généralement toujours alignée sur 4 octets.


Pas s'il s'agit d'un tableau de variables 24 bits, non?


Si vous voulez un tableau d'entre eux, vous pouvez utiliser uint8_t et comprendre l'indexation et l'emballage / le déballage vous-même.


@bgarrood, les éléments du tableau sont disposés de manière contiguë en mémoire, oui, donc si vous pouviez créer un type 24 bits, un tableau ayant ce type d'élément ne pourrait pas avoir tous les éléments alignés sur des limites de 32 bits. C'est une très bonne raison pour laquelle votre implémentation peut ne pas fournir un moyen de définir des types 24 bits. Mais là encore, ça pourrait.


4 Réponses :


3
votes

Eh bien, vous pouvez essayer de vous assurer que la structure n'occupe que l'espace dont vous avez besoin, avec quelque chose comme:

#include <stdio.h>
#include <stdint.h>

typedef struct { uint8_t byt[3]; } UInt24;
int main() {
    UInt24 x, y[2];
    printf("%zd %zd\n", sizeof(x), sizeof(y));
    return 0;
}

Vous pouvez em> doivent fournir ces directives de compilation (comme les lignes #pragma ci-dessus) pour s'assurer qu'il n'y a pas de remplissage, mais ce sera probablement la valeur par défaut pour une structure avec seulement huit bits champs (a) .

Vous devrez probablement alors emballer / décompresser des valeurs réelles vers et depuis la structure, quelque chose comme:

// Inline suggestion used to (hopefully) reduce overhead.

inline uint32_t unpack(UInt24 x) {
    uint32_t retVal = x.byt[0];
    retval = retVal << 8 | x.byt[1];
    retval = retVal << 8 | x.byt[2];
    return retVal;
}

inline UInt24 pack(uint32_t x) {
    UInt24 retVal;
    retVal.byt[0] = (x >> 16) & 0xff;
    retVal.byt[1] = (x >> 8) & 0xff;
    retVal.byt[2] = x & 0xff;
    return retVal
}


13 commentaires

Même cela ne garantit pas que le type uint24_t ne consomme que quatre octets. Le compilateur est autorisé à inclure un remplissage dans la disposition de la structure, et il le fera probablement dans ce cas. Il peut y avoir une extension disponible, pour demander au compilateur de ne pas le faire. Dans certaines implémentations, une telle chose peut être orthographiée #progma pack (1) ou similaire.


@JohnBollinger puisque uint8_t est aligné sur 1 octet, de même que uint8t_ [3] et uint24_t . Puisque uint24_t est aligné sur 1 octet, le scénario le plus évident et le plus courant est que la taille de uint24_t soit effectivement 3


@John, le cas le plus probable est qu'il ne bourdonnera pas mais, comme cela peut, j'ai ajouté une note à cet effet.


@paxdiablo, je ne sais pas sur quoi vous vous fondez pour supposer que le remplissage est peu probable. Il existe des implémentations largement utilisées qui ont une exigence d'alignement par défaut de 4 octets ou même plus pour tous les types de structure, et toute implémentation de ce type ajoutera un remplissage de fin à moins que vous ne le fassiez pas.


@John, probablement le le plus utilisé est gcc , et l'extrait de programme uint24_t x, y [2]; printf ("% zd% zd \ n", sizeof (x), sizeof (y)); donne 3 6 , ne montrant aucun remplissage à l'intérieur ou après la structure. Idem pour clang . Il peut y en avoir d'autres qui ne sont pas aussi serrés par défaut, c'est pourquoi j'ai pris votre suggestion pour la clarifier dans la réponse. Je publierai également des détails dans la réponse.


@JohnBollinger: Citation? Je ne connais aucune implémentation moderne du monde réel qui suraligne les structures.


Cela n'économise probablement pas beaucoup de RAM puisque vous obtenez une surcharge d'appel de fonction + un uint32 sur la pile. Au contraire, cela donne une utilisation maximale de la RAM beaucoup plus élevée par rapport à l'utilisation de uint32_t . La profondeur de la pile est une préoccupation majeure sur les systèmes où la RAM est restreinte. Vous devez vous assurer que les fonctions sont intégrées, au minimum.


@Lundin, mon hypothèse était que l'OP avait un grand tableau de ces valeurs et gaspillait 25% quand ils prenaient 32 bits chacun. Une structure 24 bits peut atténuer cela et, oui, il y a un coût, à la fois en termes de performances de pack / décompression et de code supplémentaire, les utilisateurs doivent décider si cela en vaut la peine. Je n'ai pas pris la peine de faire des inserts car, pour être honnête, la inline suggestion est souvent rendue inutile par les compilateurs modernes qui peuvent trouver la meilleure façon de faire des choses en ligne. Cela fait un moment que je n'ai pas essayé de deviner les compilateurs incroyablement optimisés ces derniers temps :-) Je vais l'ajouter cependant comme suggestion.


@R ..: Bon point, j'ai renommé le type. Pour les personnes intéressées, ces types sont spécifiés comme réservés dans C11 7.31 Futures directions de la bibliothèque - Tous les noms externes décrits ci-dessous sont réservés quels que soient les en-têtes inclus par le programme en conjonction avec 7.31.10 < stdint.h> Les noms de typedef commençant par int ou uint et se terminant par _t peuvent être ajoutés aux types définis dans l'en-tête .


@paxdiablo Il y a de grandes chances que ce soit pour le tristement célèbre PIC, compilé avec le MPLAB encore plus infâme, plutôt qu'avec un compilateur moderne.


Ne devrait-on pas prendre en compte l'alignement de la mémoire en général si le but est d'économiser de la RAM? Par exemple, si je déclare trois variables gloval comme UInt24 varx, variez [2]; uint32_t varz; puis vérifiez le fichier output.map généré avec gcc -Xlinker -Map = output.map, j'obtiens varx aligné sur 4 octets dans le secteur .bss. Même comportement avec l'option -Os qui optimise la taille.


@matteo, encore une fois cela dépend de l'environnement. Je n'ai proposé qu'une manière dont vous pourriez économiser de la mémoire, cela peut ou non fonctionner tel quel sur chaque environnement. Je serais intéressé de voir votre disposition pour la variable varier , je suppose que cela ne montrerait aucun alignement 32 bits, ce qui signifie qu'un grand tableau de ces valeurs serait certainement plus efficace en termes d'espace que uint32_t . Mais encore une fois, ce n'est que pour un environnement, les utilisateurs de ces informations doivent vérifier leurs propres résultats.


@paxdiablo Je comprends votre point de vue et je suis d'accord que dans certaines circonstances, votre solution pourrait être un moyen efficace d'économiser de la RAM, en particulier pour les grandes baies. Cependant, je suppose qu'il pourrait être utile que le PO prenne également en considération l'alignement.



2
votes

Un commentaire de João Baptista sur ce site indique que vous pouvez utiliser #pragma pack . Une autre option est d'utiliser __attribute__((packed)):

#ifndef __GNUC__
# define __attribute__(x)
#endif
struct uint24_t { unsigned long v:24; };
typedef struct uint24_t __attribute__((packed)) uint24_t;

Cela devrait fonctionner sur GCC et Clang.

Notez cependant que cela gâchera probablement l'alignement à moins que votre processeur ne prenne en charge l'accès non aligné.


1 commentaires

L'emballage / contrôle du remplissage est une considération, mais si vous utilisez un champ de bits, la taille et le nombre de ses unités de stockage sous-jacentes sont distincts, donc l'emballage n'a pas nécessairement l'effet que vous pourriez attendre ici. Mais je trouve que cela fait cet effet avec GCC, donc très agréable.



1
votes

Au départ, je pensais que c'était parce que cela obligeait les types de données à être alignés sur les mots

Différents types de données peuvent avoir un alignement différent. Voir par exemple le document Objets et alignement .

Vous pouvez utiliser alignof pour vérifier, mais il est tout à fait normal que char ou uint8_t ait 1 octet (c'est-à-dire effectivement non) alignement, mais pour que uint32_t ait un alignement de 4 octets. Je ne sais pas si l'alignement des champs de bits est explicitement décrit, mais l'hériter du type de stockage semble assez raisonnable.

NB. La raison d'avoir des exigences d'alignement est généralement que cela fonctionne mieux avec le matériel sous-jacent. Si vous utilisez #pragma pack ou __attribute __ ((compressé)) ou quoi que ce soit, vous pouvez prendre un coup de performance en tant que compilateur - ou le matériel de mémoire - gère silencieusement les accès mal alignés.

Le simple stockage explicite d'un tableau de 3 octets est probablement mieux, IMO.


0 commentaires

0
votes

Pour commencer, n'utilisez pas de champs de bits ou de structures. Ils peuvent inclure un remplissage à leur guise et les champs de bits ne sont généralement pas portables.

À moins que votre CPU n'ait explicitement des instructions arithmétiques 24 bits - ce qui ne semble pas très probable à moins qu'il ne s'agisse d'un DSP bizarre - alors un type de données personnalisé n'obtiennent rien d'autre qu'un ballonnement supplémentaire.

Vous devrez probablement utiliser uint32_t pour toute l'arithmétique. Cela signifie que votre type 24 bits pourrait ne pas faire grand-chose en matière d'économie de RAM. Si vous inventez un ADT personnalisé avec un accès setter / getter (sérialisation / désérialisation), vous êtes probablement en train de gaspiller de la RAM puisque vous obtenez une utilisation de pic de pile plus élevée si les fonctions ne peuvent pas être intégrées. p>

Pour réellement économiser de la RAM, vous devriez plutôt réviser la conception de votre programme.


Cela étant dit, vous pouvez créer un type personnalisé basé sur un tableau:

u24_t    u24;
uint32_t u32;
...
memcpy(&u32, u24, sizeof(u24));
...
memcpy(&u24, &u32, sizeof(u24));

Chaque fois que vous avez besoin d'accéder aux données, vous les memcpy vers / depuis un type 32 bits, puis faites tout l'arithmétique sur 32 bits:

typedef unsigned char u24_t[3];


0 commentaires