web-dev-qa-db-fra.com

C / C ++ Pourquoi utiliser un caractère non signé pour les données binaires?

Est-il vraiment nécessaire d'utiliser unsigned char Pour contenir des données binaires comme dans certaines bibliothèques qui fonctionnent sur le codage de caractères ou les tampons binaires? Pour donner un sens à ma question, jetez un œil au code ci-dessous -

char c[5], d[5];
c[0] = 0xF0;
c[1] = 0xA4;
c[2] = 0xAD;
c[3] = 0xA2;
c[4] = '\0';

printf("%s\n", c);
memcpy(d, c, 5);
printf("%s\n", d);

à la fois la sortie printf's???? correctement, où f0 a4 ad a2 est le codage pour le point de code Unicode U+24B62 (????) en hexadécimal.

Même memcpy a également copié correctement les bits détenus par un caractère.

Quel raisonnement pourrait éventuellement préconiser l'utilisation de unsigned char Au lieu d'un plain char?

Dans d'autres questions connexes, unsigned char Est mis en évidence car c'est le seul type de données (octet/plus petit) qui est garanti sans remplissage par la spécification C. Mais comme l'exemple ci-dessus l'a montré, la sortie ne semble pas être affectée par un remplissage en tant que tel.

J'ai utilisé VC++ Express 2010 et MinGW pour compiler ce qui précède. Bien que VC a donné l'avertissement

warning C4309: '=' : truncation of constant value

la sortie ne semble pas refléter cela.

P.S. Cela pourrait être marqué comme un doublon possible de n tampon d'octets doit-il être un tampon de caractères signé ou non signé? mais mon intention est différente. Je demande pourquoi quelque chose qui semble fonctionner aussi bien avec char devrait être tapé unsigned char?

Mise à jour: Pour citer N3337,

Section 3.9 Types

2 Pour tout objet (autre qu'un sous-objet de classe de base) de type T copiquement trivial, que l'objet contienne ou non une valeur valide de type T, les octets sous-jacents (1.7) constituant l'objet peuvent être copiés dans un tableau de caractères ou caractère non signé. Si le contenu du tableau de caractères ou de caractères non signés est recopié dans l'objet, l'objet conservera par la suite sa valeur d'origine.

Compte tenu du fait ci-dessus et que mon exemple d'origine était sur une machine Intel où char par défaut à signed char, Je ne suis toujours pas convaincu si unsigned char Devrait être préféré à char.

Rien d'autre?

48
nightlytrails

En C, le unsigned char le type de données est le seul type de données qui possède simultanément les trois propriétés suivantes

  • il n'a pas de bits de remplissage, c'est-à-dire que tous les bits de stockage contribuent à la valeur des données
  • aucune opération au niveau du bit à partir d'une valeur de ce type, lorsqu'elle est reconvertie dans ce type, ne peut produire un débordement, des représentations d'interruption ou un comportement non défini
  • il peut alias d'autres types de données sans violer les "règles d'alias", c'est-à-dire que l'accès aux mêmes données via un pointeur qui est tapé différemment sera garanti pour voir toutes les modifications

si ce sont les propriétés d'un type de données "binaire" que vous recherchez, vous devez définitivement utiliser unsigned char.

Pour la deuxième propriété, nous avons besoin d'un type qui est unsigned. Pour celles-ci toutes les conversions sont définies avec modulo arihmetic, ici modulo UCHAR_MAX+1, 256 dans la plupart des 99% des architectures. Toute conversion de valeurs plus larges en unsigned char correspond ainsi à la troncature de l'octet le moins significatif.

Les deux autres types de caractères ne fonctionnent généralement pas de la même manière. signed char est signé de toute façon, donc la conversion des valeurs qui ne lui correspondent pas n'est pas bien définie. char n'est pas fixé pour être signé ou non signé, mais sur une plate-forme particulière sur laquelle votre code est porté, il peut être signé même s'il n'est pas signé sur le vôtre.

83
Jens Gustedt

Vous obtiendrez la plupart de vos problèmes lors de la comparaison du contenu d'octets individuels:

char c[5];
c[0] = 0xff;
/*blah blah*/
if (c[0] == 0xff)
{
    printf("good\n");
}
else
{
    printf("bad\n");
}

peut afficher "mauvais", car, selon votre compilateur, c [0] sera le signe étendu à -1, ce qui n'est pas du tout la même chose que 0xff

13
Tom Tanner

Le type plain char est problématique et ne doit pas être utilisé pour autre chose que des chaînes. Le principal problème avec char est que vous ne pouvez pas savoir s'il est signé ou non: c'est un comportement défini par l'implémentation. Cela rend char différent de int etc, int est toujours garanti d'être signé.

Bien que VC a donné l'avertissement ... troncature de la valeur constante

Cela vous indique que vous essayez de stocker des littéraux int dans des variables char. Cela peut être lié à la signature: si vous essayez de stocker un entier avec une valeur> 0x7F dans un caractère signé, des choses inattendues peuvent se produire. Formellement, il s'agit d'un comportement indéfini en C, bien que pratiquement vous obtiendrez simplement une sortie étrange si vous essayez d'imprimer le résultat sous la forme d'une valeur entière stockée dans un caractère (signé).

Dans ce cas spécifique, l'avertissement ne devrait pas avoir d'importance.

MODIFIER:

Dans d'autres questions connexes, le caractère non signé est mis en évidence parce que c'est le seul type de données (octet/plus petit) qui est garanti sans remplissage par la spécification C.

En théorie, tous les types entiers, à l'exception du caractère non signé et du caractère signé, peuvent contenir des "bits de remplissage", conformément à C11 6.2.6.2:

"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 (il n'est pas nécessaire qu'il y en ait un de ces derniers)."

"Pour les types entiers signés, les bits de la représentation d'objet doivent être divisés en trois groupes: les bits de valeur, les bits de remplissage et le bit de signe. Il n'y a pas besoin de bits de remplissage; le caractère signé ne doit pas avoir de bits de remplissage."

La norme C est intentionnellement vague et floue, permettant ces bits de remplissage théoriques car:

  • Il autorise des tables de symboles différentes de celles standard à 8 bits.
  • Il permet la signature définie par l'implémentation et des formats entiers signés étranges tels que son complément ou "signe et ampleur".
  • Un entier peut ne pas nécessairement utiliser tous les bits alloués.

Cependant, dans le monde réel en dehors de la norme C, ce qui suit s'applique:

  • Les tables de symboles sont presque certainement de 8 bits (UTF8 ou ASCII). Certaines exceptions étranges existent, mais les implémentations propres utilisent le type standard wchar_t lors de l'implémentation de tables de symboles de plus de 8 bits.
  • La signature est toujours un complément à deux.
  • Un entier utilise toujours tous les bits alloués.

Il n'y a donc aucune raison réelle d'utiliser un caractère non signé ou un caractère signé juste pour esquiver un scénario théorique dans la norme C.

12
Lundin

Les octets sont généralement conçus comme des entiers non signés de 8 bits de large.

Maintenant, char ne spécifie pas le signe de l'entier: sur certains compilateurs, char peut être signé, sur d'autres il peut ne pas être signé.

Si j'ajoute un peu d'opération de décalage au code que vous avez écrit, alors j'aurai un comportement indéfini. La comparaison ajoutée aura également un résultat inattendu.

char c[5], d[5];
c[0] = 0xF0;
c[1] = 0xA4;
c[2] = 0xAD;
c[3] = 0xA2;
c[4] = '\0';
c[0] >>= 1; // If char is signed, will the 7th bit go to 0 or stay the same?

bool isBiggerThan0 = c[0] > 0; // FALSE if char is signed!

printf("%s\n", c);
memcpy(d, c, 5);
printf("%s\n", d);

Concernant l'avertissement lors de la compilation: si le caractère est signé, vous essayez d'attribuer la valeur 0xf0, qui ne peut pas être représentée dans le caractère signé (plage -128 à +127), donc il sera converti en valeur signée (- 16).

Déclarer le caractère non signé supprimera l'avertissement, et il est toujours bon d'avoir une construction propre sans aucun avertissement.

6
Paolo Brandoli

La signature-ness du type plain char est définie par l'implémentation, donc à moins que vous n'ayez réellement affaire à des données de caractères (une chaîne utilisant le jeu de caractères de la plateforme - généralement ASCII), il est généralement préférable de spécifier la signature-ness explicitement en utilisant signed char ou unsigned char.

Pour les données binaires, le meilleur choix est très probablement unsigned char, surtout si des opérations au niveau du bit seront effectuées sur les données (en particulier le décalage de bits, qui ne se comporte pas de la même manière pour les types signés que pour les types non signés).

4
Sander De Dycker

Est-il vraiment nécessaire d'utiliser un caractère non signé pour contenir des données binaires comme dans certaines bibliothèques qui fonctionnent sur le codage de caractères ou les tampons binaires?

"vraiment" nécessaire? Non.

C'est cependant une très bonne idée, et il y a plusieurs raisons à cela.

Votre exemple utilise printf, qui ne saisit pas le type. Autrement dit, printf prend ses repères de mise en forme à partir de la chaîne de format et non du type de données. Vous pouvez tout aussi facilement essayer:

printf("%s\n", (void*)c);

... et le résultat aurait été le même. Si vous essayez la même chose avec les iostreams c ++, le résultat sera différent (selon la signature de ness).

Quel raisonnement pourrait éventuellement préconiser l'utilisation d'un caractère non signé au lieu d'un caractère ordinaire?

Non signé spécifie que le bit le plus significatif des données (pour le caractère non signé le 8ème bit) représente le signe. Comme vous n'avez évidemment pas besoin de cela, vous devez spécifier que vos données ne sont pas signées (le bit "signe" représente les données, pas le signe des autres bits).

2
utnapistim

Je demande pourquoi quelque chose qui semble fonctionner aussi bien avec char devrait être tapé char non signé?

Si vous faites des choses qui ne sont pas "correctes" au sens de la norme, vous vous fiez à un comportement non défini. Votre compilateur peut le faire comme vous le souhaitez aujourd'hui, mais vous ne savez pas ce qu'il fera demain. Vous ne savez pas ce que fait GCC ou VC++ 2012. Ou même si le comportement dépend de facteurs externes ou de compilations Debug/Release, etc. Dès que vous quittez le chemin sécurisé de la norme, vous pouvez rencontrer des problèmes.

2
Philipp

Eh bien, comment appelez-vous les "données binaires"? Il s'agit d'un tas de bits, sans aucune signification qui leur est attribuée par la partie spécifique du logiciel qui les appelle "données binaires". Quel est le type de données primitif le plus proche, qui transmet l'idée de l'absence de signification spécifique à l'un de ces bits? Je pense unsigned char.

2
chill