uint8_t vs caractère non signé

231

Quel est l'avantage d'utiliser uint8_tover unsigned charen C?

Je sais que sur presque tous les systèmes, ce uint8_tn'est qu'un typedef unsigned char, alors pourquoi l'utiliser?

Lyndon White
la source

Réponses:

225

Il documente votre intention - vous stockerez de petits nombres, plutôt qu'un caractère.

De plus, cela semble plus agréable si vous utilisez d'autres types de caractères tels que uint16_tou int32_t.

Mark Ransom
la source
1
Il n'était pas clair dans la question initiale si nous parlions d'un type standard ou non. Je suis sûr qu'il y a eu de nombreuses variantes de cette convention de dénomination au fil des ans.
Mark Ransom
8
Utiliser unsigned charou signed chardocumenter explicitement l'intention aussi, car sans fioritures charest ce qui montre que vous travaillez avec des personnages.
caf
9
Je pensais qu'un sans ornement unsignedétait unsigned intpar définition?
Mark Ransom
5
@endolith, utiliser uint8_t pour une chaîne n'est pas nécessairement faux, mais c'est vraiment bizarre.
Mark Ransom
5
@endolith, je pense que je peux faire un cas pour uint8_t avec du texte UTF8. En effet, charsemble impliquer un caractère, alors que dans le contexte d'une chaîne UTF8, il peut ne s'agir que d'un octet d'un caractère multi-octets. L'utilisation de uint8_t pourrait indiquer clairement qu'il ne faut pas s'attendre à un caractère à chaque position - en d'autres termes que chaque élément de la chaîne / du tableau est un entier arbitraire sur lequel il ne faut pas faire d'hypothèses sémantiques. Bien sûr, tous les programmeurs C le savent, mais cela peut pousser les débutants à poser les bonnes questions.
TNE
70

Juste pour être pédant, certains systèmes peuvent ne pas avoir un type 8 bits. Selon Wikipedia :

Une implémentation est requise pour définir des types entiers de largeur exacte pour N = 8, 16, 32 ou 64 si et seulement si elle a un type qui répond aux exigences. Il n'est pas nécessaire de les définir pour tout autre N, même s'il prend en charge les types appropriés.

Il uint8_tn'est donc pas garanti d'exister, bien qu'il le soit pour toutes les plates-formes où 8 bits = 1 octet. Certaines plates-formes intégrées peuvent être différentes, mais cela devient très rare. Certains systèmes peuvent définir des chartypes sur 16 bits, auquel cas il n'y aura probablement aucun type sur 8 bits.

À part ce problème (mineur), la réponse de @Mark Ransom est la meilleure à mon avis. Utilisez celui qui montre le plus clairement pourquoi vous utilisez les données.

En outre, je suppose que vous vouliez dire uint8_t(le typedef standard de C99 fourni dans l'en- stdint.htête) plutôt que uint_8(ne faisant partie d'aucune norme).

Chris Lutz
la source
3
@caf, par pure curiosité - pouvez-vous lier à la description de certains? Je sais qu'ils existent parce que quelqu'un en a mentionné un (et lié à des documents pour les développeurs) dans une discussion modérée comp.lang.c ++ pour savoir si les garanties de type C / C ++ sont trop faibles, mais je ne trouve plus ce thread, et c'est toujours pratique de faire référence à cela dans des discussions similaires :)
Pavel Minaev
3
"Certains systèmes peuvent définir des types de caractères sur 16 bits, auquel cas il n'y aura probablement pas de type 8 bits d'aucune sorte." - et malgré quelques objections incorrectes de ma part, Pavel a démontré dans sa réponse que si char est de 16 bits, alors même si le compilateur fournit un type 8 bits, il ne doit pas l' appeler uint8_t(ou le taper typef pour cela). En effet, le type 8 bits aurait des bits inutilisés dans la représentation de stockage, qui uint8_tne doivent pas avoir.
Steve Jessop
3
L'architecture SHARC a des mots de 32 bits. Voir en.wikipedia.org/wiki/… pour plus de détails.
BCran
2
Et les DSP C5000 de TI (qui étaient en OMAP1 et OMAP2) sont en 16 bits. Je pense que pour OMAP3, ils sont passés à la série C6000, avec un caractère 8 bits.
Steve Jessop
4
En creusant dans N3242 - "Working Draft, Standard for Programming Language C ++", section 18.4.1 <cstdint> synopsis says - typedef unsigned integer type uint8_t; // optional Donc, en substance, une bibliothèque conforme à la norme C ++ n'est pas nécessaire du tout pour définir uint8_t (voir le commentaire // optionnel )
nightlytrails
43

Le but est d'écrire du code indépendant de l'implémentation. unsigned charn'est pas garanti comme étant de type 8 bits. uint8_test (si disponible).

Fourmi
la source
4
... si elle existe sur un système, mais ça va être très rare. +1
Chris Lutz
2
Eh bien, si vous avez vraiment eu des problèmes avec votre code ne se compilant pas sur un système parce que uint8_t n'existait pas, vous pouvez utiliser find et sed pour changer automatiquement toutes les occurrences de uint8_t en char non signé ou quelque chose de plus utile pour vous.
bazz
2
@bazz - pas si vous supposez qu'il s'agit d'un type 8 bits que vous ne pouvez pas - par exemple pour décompresser des données empaquetées par octets par un système distant. L'hypothèse implicite est que la raison pour laquelle uint8_t n'existe pas est sur un processeur où un caractère est supérieur à 8 bits.
Chris Stratton
ajouter l'assertion assert (sizeof (unsigned char) == 8);
bazz
3
@bazz assertion incorrecte, je le crains. sizeof(unsigned char)retournera 1pour 1 octet. mais si un système char et int ont la même taille, par exemple, 16 bits, alors sizeof(int)ils reviendront également1
Toby
7

Comme vous l'avez dit, " presque tous les systèmes".

charest probablement l'un des moins susceptibles de changer, mais une fois que vous commencez à utiliser uint16_tet vos amis, vous utilisez uint8_tmieux les mélanges, et peut même faire partie d'une norme de codage.

Juste amoureux
la source
7

D'après mon expérience, il y a deux endroits où nous voulons utiliser uint8_t pour signifier 8 bits (et uint16_t, etc.) et où nous pouvons avoir des champs inférieurs à 8 bits. Les deux endroits sont où l'espace compte et nous avons souvent besoin de regarder un vidage brut des données lors du débogage et de pouvoir déterminer rapidement ce qu'il représente.

Le premier concerne les protocoles RF, en particulier dans les systèmes à bande étroite. Dans cet environnement, nous devrons peut-être regrouper autant d'informations que possible dans un seul message. Le second est dans le stockage flash où nous pouvons avoir un espace très limité (comme dans les systèmes embarqués). Dans les deux cas, nous pouvons utiliser une structure de données compressée dans laquelle le compilateur se chargera de l'emballage et du déballage pour nous:

#pragma pack(1)
typedef struct {
  uint8_t    flag1:1;
  uint8_t    flag2:1;
  padding1   reserved:6;  /* not necessary but makes this struct more readable */
  uint32_t   sequence_no;
  uint8_t    data[8];
  uint32_t   crc32;
} s_mypacket __attribute__((packed));
#pragma pack()

La méthode que vous utilisez dépend de votre compilateur. Vous devrez peut-être également prendre en charge plusieurs compilateurs différents avec les mêmes fichiers d'en-tête. Cela se produit dans les systèmes embarqués où les périphériques et les serveurs peuvent être complètement différents - par exemple, vous pouvez avoir un périphérique ARM qui communique avec un serveur Linux x86.

Il y a quelques mises en garde concernant l'utilisation de structures compactes. Le plus gros problème est que vous devez éviter de déréférencer l'adresse d'un membre. Sur les systèmes avec des mots alignés sur plusieurs octets, cela peut entraîner une exception mal alignée - et un coredump.

Certaines personnes s'inquiéteront également des performances et soutiendront que l'utilisation de ces structures compressées ralentira votre système. Il est vrai que, dans les coulisses, le compilateur ajoute du code pour accéder aux membres de données non alignés. Vous pouvez le voir en regardant le code assembleur dans votre IDE.

Mais comme les structures compressées sont les plus utiles pour la communication et le stockage des données, les données peuvent être extraites dans une représentation non compressée lorsque vous travaillez avec elles en mémoire. Normalement, nous n'avons pas besoin de travailler avec le paquet de données entier en mémoire de toute façon.

Voici quelques discussions pertinentes:

pragma pack (1) ni __attribute__ ((aligné (1))) fonctionne

Le pack __attribute __ ((emballé)) / #pragma de gcc est-il dangereux?

http://solidsmoke.blogspot.ca/2010/07/woes-of-structure-packing-pragma-pack.html

Tereus Scott
la source
6

Il y a peu. Du point de vue de la portabilité, charne peut pas être inférieur à 8 bits, et rien ne peut être inférieur à char, donc si une implémentation C donnée a un type entier 8 bits non signé, ce sera le cas char. Alternativement, il peut ne pas en avoir du tout, à quel point les typedefastuces sont sans objet.

Il pourrait être utilisé pour mieux documenter votre code dans un sens qu'il est clair que vous avez besoin d'octets 8 bits et rien d'autre. Mais dans la pratique, c'est une attente raisonnable pratiquement n'importe où déjà (il existe des plates-formes DSP sur lesquelles ce n'est pas vrai, mais les chances que votre code s'exécute là-bas sont minces, et vous pourriez tout aussi bien vous tromper en utilisant une assertion statique en haut de votre programme sur une telle plateforme).

Pavel Minaev
la source
7
@Skizz - Non, la norme exige unsigned charde pouvoir contenir des valeurs entre 0 et 255. Si vous pouvez le faire en 4 bits, mon chapeau est à vous.
Chris Lutz
1
"ce serait un peu plus encombrant" - encombrant dans le sens où il faudrait marcher (nager, prendre un avion, etc.) jusqu'à l'endroit où se trouvait l'auteur du compilateur, les gifler à l'arrière de la tête et les ajouter uint8_tà l'implémentation. Je me demande, les compilateurs pour DSP avec des caractères 16 bits implémentent-ils généralement uint8_tou non?
Steve Jessop
6
Soit dit en passant, après réflexion, c'est peut-être la façon la plus simple de dire "J'ai vraiment besoin de 8 bits" - #include <stdint.h>et d'utiliser uint8_t. Si la plate-forme l'a, elle vous la donnera. Si la plate-forme ne l'a pas, votre programme ne sera pas compilé et la raison sera claire et simple.
Pavel Minaev,
2
Toujours pas de cigare, désolé: "Pour les types entiers non signés autres que le caractère non signé, les bits de la représentation d'objet doivent être divisés en deux groupes: les bits de valeur et les bits de remplissage ... S'il y a N bits de valeur, chaque bit doit représenter un différent puissance de 2 entre 1 et 2 ^ (N-1), de sorte que les objets de ce type soient capables de représenter des valeurs de 0 à 2 ^ (N-1) en utilisant une représentation binaire pure ... Le nom de typedef intN_t désigne un type entier signé avec largeur N, pas de bits de remplissage et une représentation du complément à deux. "
Pavel Minaev,
1
Si vous avez juste besoin d'un module arithmétique, le champ de bits non signé fera l'affaire (si cela ne vous convient pas). C'est quand vous avez besoin, disons, d'un tableau d'octets sans remplissage, c'est quand vous êtes SOL. La morale de l'histoire n'est pas de coder pour les DSP, et de s'en tenir aux architectures de caractères 8 bits honnêtes à Dieu :)
Pavel Minaev
4

C'est très important par exemple lorsque vous écrivez un analyseur de réseau. les en-têtes de paquet sont définis par la spécification du protocole, et non par le fonctionnement du compilateur C d'une plate-forme particulière.

VP.
la source
à l'époque où j'ai demandé cela, j'ai été défini un protocole simple de communication sur série.
Lyndon White
2

Sur presque tous les systèmes, j'ai rencontré uint8_t == char non signé, mais ce n'est pas garanti par la norme C. Si vous essayez d'écrire du code portable et que la taille de la mémoire est exacte, utilisez uint8_t. Sinon, utilisez un caractère non signé.

atlpeg
la source
3
uint8_t correspond toujours à la plage et à la taille de unsigned charet au remplissage (aucun) lorsqu'il unsigned char est à 8 bits. Quand unsigned charn'est pas 8 bits, uint8_tn'existe pas.
chux
@chux, avez-vous une référence à l'endroit exact dans la norme où il est dit cela? Si unsigned charest 8 bits, est uint8_tgaranti d'être un de typedefceux - ci et non un typedefd'un type entier non signé étendu ?
hsivonen
@hsivonen "endroit exact dans la norme où il est dit cela?" -> Non - regardez encore à 7.20.1.1. Il est facilement déduit comme unsigned char/signed char/charle type le plus petit - pas plus petit que 8 bits. unsigned charn'a pas de rembourrage. Pour uint8_têtre, il doit être de 8 bits, pas de remplissage, exister à cause d'une implémentation de type entier fourni: correspondant aux exigences minimales de unsigned char. Quant à "... garanti d'être un typedef ..." ressemble à une bonne question à poster.
chux