545 votes

Qu'est-ce qu'un unsigned char?

En C/C++, à quoi sert un unsigned char? En quoi est-il différent d'un char ordinaire?

615voto

Fruny Points 2713

En C++, il existe trois types de caractères distincts:

  1. char
  2. signed char
  3. unsigned char

1. char

Si vous utilisez des types de caractères pour du texte, utilisez le char non qualifié:

  • il s'agit du type des littéraux de caractère comme 'a' ou '0' (en C++ seulement, en C leur type est int)
  • il s'agit du type qui compose les chaînes de caractères C comme "abcde"

Cela fonctionne également comme une valeur numérique, mais il n'est pas spécifié si cette valeur est traitée comme signée ou non signée. Méfiez-vous des comparaisons de caractères par les inégalités - bien que si vous vous limitez à l'ASCII (0-127), vous êtes à peu près en sécurité.

2. signed char/ 3. unsigned char

Si vous utilisez des types de caractères comme des nombres, utilisez:

  • signed char, qui vous donne au moins la plage de -127 à 127. (-128 à 127 est courant)
  • unsigned char, qui vous donne au moins la plage de 0 à 255. Cela pourrait être utile pour afficher un octet par exemple en valeur hexadécimale.

"Au moins", car la norme C++ ne donne que la plage minimale de valeurs que chaque type numérique est censé couvrir. sizeof (char) est censé être 1 (c'est-à-dire un octet), mais un octet pourrait en théorie être par exemple sur 32 bits. sizeof rapporterait toujours sa taille comme 1 - ce qui signifie que vous pourriez avoir sizeof (char) == sizeof (long) == 1.

5 votes

Pour être clair, pourriez-vous avoir des caractères sur 32 bits et des entiers sur 32 bits, et avoir sizeof(int) != sizeof(char) ? Je sais que la norme dit que sizeof(char) == 1, mais est-ce que sizeof(int) relatif est basé sur une différence réelle de taille ou sur la différence de plage ?

0 votes

Joseph, la fonction sizeof vous donne la taille de l'objet représentation du type. si vous dites 32 bits entier, cela ne dit pas grand-chose. très probablement vous voulez dire la représentation de l'objet (c'est la taille physique - y compris tous les bits de rembourrage).

0 votes

Si c'est le cas, alors sizeof(int) != sizeof(char) ne peut pas être vrai, car char/unsigned/signed char utilisent tous les bits de leur représentation d'objet pour représenter leurs valeurs (appelée la représentation de la valeur)

106voto

tgamblin Points 25755

Cela dépend de l'implémentation, car la norme C NE définit PAS la signe de char. Selon la plateforme, char peut être signed ou unsigned, donc vous devez explicitement demander signed char ou unsigned char si votre implémentation en dépend. Utilisez simplement char si vous voulez représenter des caractères à partir de chaînes, car cela correspondra à ce que votre plateforme met dans la chaîne.

La différence entre signed char et unsigned char est comme vous le pensez. Sur la plupart des plateformes, signed char sera un nombre de 8 bits en complément à deux allant de -128 à 127, et unsigned char sera un entier non signé de 8 bits (0 à 255). Notez que la norme N'oblige PAS les types char à avoir 8 bits, seulement que sizeof(char) retourne 1. Vous pouvez obtenir le nombre de bits dans un char avec CHAR_BIT dans limits.h. Il y a peu de plateformes aujourd'hui où cela sera autre chose que 8, cependant.

Il y a un résumé intéressant de cette question ici.

Comme d'autres l'ont mentionné depuis que j'ai posté ceci, il est préférable d'utiliser int8_t et uint8_t si vous voulez vraiment représenter de petits entiers.

3 votes

Char signé n'a qu'une plage minimale de -127 à 127, pas de -128 à 127

6 votes

@12431234123412341234123: Techniquement vrai, dans la mesure où la norme C définit -127 à 127 comme la plage minimum. Je vous mets au défi de trouver une plateforme n'utilisant pas l'arithmétique en complément à deux. Sur presque toutes les plateformes modernes, la plage réelle des char signés sera de -128 à 127.

0 votes

CHAR_BIT doit être d'au moins 8 bits selon la norme.

45voto

Parce que je pense que c'est vraiment nécessaire, je veux simplement énoncer quelques règles de C et C++ (elles sont les mêmes à cet égard). Tout d'abord, tous les bits de unsigned char participent à la détermination de la valeur si un objet unsigned char. Deuxièmement, unsigned char est explicitement déclaré unsigned.

Maintenant, j'ai eu une discussion avec quelqu'un sur ce qui se passe lorsque vous convertissez la valeur -1 de type int en unsigned char. Il a refusé l'idée que le unsigned char résultant a tous ses bits définis à 1, car il était préoccupé par la représentation du signe. Mais il n'avait pas à l'être. Il découle immédiatement de cette règle que la conversion fait ce qui est prévu:

Si le nouveau type est unsigned, la valeur est convertie en ajoutant ou soustrayant de manière répétée une unité de plus que la valeur maximale pouvant être représentée dans le nouveau type jusqu'à ce que la valeur soit dans la plage du nouveau type. (6.3.1.3p2 dans un brouillon de C99)

C'est une description mathématique. C++ le décrit en termes de calcul modulo, ce qui conduit à la même règle. Quoi qu'il en soit, ce qui n'est pas garanti, c'est que tous les bits dans l'entier -1 soient à un avant la conversion. Alors, que pouvons-nous dire pour affirmer que le unsigned char résultant a tous ses bits de CHAR_BIT réglés à 1?

  1. Tous les bits participent à la détermination de sa valeur - c'est-à-dire qu'aucun bit de remplissage n'apparaît dans l'objet.
  2. Ajouter une seule fois UCHAR_MAX+1 à -1 donnera une valeur dans la plage, à savoir UCHAR_MAX

C'est suffisant, en fait! Donc chaque fois que vous voulez avoir un unsigned char ayant tous ses bits à un, vous faites

unsigned char c = (unsigned char)-1;

Cela signifie également qu'une conversion ne se limite pas à la simple troncation des bits de plus fort poids. L'événement favorable pour le complément à deux est que c'est juste une troncation là, mais la même chose n'est pas nécessairement vraie pour d'autres représentations de signe.

2 votes

Pourquoi ne pas simplement utiliser UCHAR_MAX ?

1 votes

Parce que (type non signé)-1 est une sorte d'idiome. ~0 ne l'est pas.

1 votes

Si j'ai quelque chose comme ceci int x = 1234 et char *y = &x. La représentation binaire de 1234 est 00000000 00000000 00000100 11010010. Ma machine est de petit boutisme donc elle inverse et stocke en mémoire 11010010 00000100 00000000 00000000 avec le LSB en premier. Maintenant la partie principale. Si j'utilise printf("%d", *p), printf va lire le premier octet 11010010 seulement la sortie est -46 mais 11010010 est 210 donc pourquoi imprime-t-il -46. Je suis vraiment confus je suppose qu'une sorte de promotion de char à entier est en train de se faire mais je ne sais pas.

33voto

Zachary Garrett Points 221

Quant aux exemples d'utilisation de unsigned char :

unsigned char est souvent utilisé en infographie, qui associe très souvent (mais pas toujours) un octet à chaque composant de couleur. Il est courant de voir une couleur RGB (ou RGBA) représentée sur 24 (ou 32) bits, chaque composante étant un unsigned char. Étant donné que les valeurs d'un unsigned char se situent dans la plage [0,255], ces valeurs sont généralement interprétées comme suit :

  • 0 signifiant l'absence totale d'un composant de couleur donné.
  • 255 signifiant 100% d'un pigment de couleur donné.

Vous vous retrouveriez donc avec du rouge RGB en tant que (255,0,0) -> (100% rouge, 0% vert, 0% bleu).

Pourquoi ne pas utiliser un signed char ? Les opérations arithmétiques et les décalages de bits posent problème. Comme expliqué précédemment, la plage d'un signed char est essentiellement décalée de -128. Une méthode très simple et naïve (largement inutilisée) pour convertir du RGB en niveaux de gris est de faire la moyenne de tous les composants de couleur, mais cela pose problème lorsque les valeurs des composants de couleur sont négatives. Le rouge (255, 0, 0) se transforme en (85, 85, 85) en utilisant l'arithmétique des unsigned char. Cependant, si les valeurs étaient des signed char (127,-128,-128), nous obtiendrions (-99, -99, -99), ce qui correspondrait à (29, 29, 29) dans notre espace de unsigned char, ce qui est incorrect.

1 votes

Je dois peut-être manquer quelque chose mais je ne comprends pas comment un décalage fixe pourrait rompre une moyenne arithmétique. La moyenne de 127, -128 et -128 est -43, pas -99. Si vous ajoutez 128 à cela, vous obtenez 85, ce qui est le même que votre exemple non signé.

13voto

jbleners Points 672

Si vous voulez utiliser un caractère comme un petit entier, la manière la plus sûre de le faire est avec les types int8_t et uint8_t.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X