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?
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
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.
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
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:
Cependant, dans le monde réel en dehors de la norme C, ce qui suit s'applique:
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.
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.
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).
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).
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.
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
.