C / C ++ Pourquoi utiliser des caractères non signés pour les données binarys?

Est-il vraiment nécessaire d’utiliser des caractères unsigned char pour contenir des données binarys, comme dans certaines bibliothèques qui travaillent sur le codage de caractères ou les tampons binarys? Pour donner un sens à ma question, regardez le 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 l’encodage du sharepoint code Unicode U+24B62 (𤭢) en hexadécimal.

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

Quel raisonnement pourrait éventuellement préconiser l’utilisation de caractères unsigned char au lieu d’un caractère plain char ?

Dans d’autres questions apparentées, le caractère unsigned char est mis en surbrillance car il s’agit du seul type de données (octet / plus petit) dont la spécification C garantit l’absence de remplissage. Mais comme le montre l’exemple ci-dessus, la sortie ne semble pas être affectée par un remplissage en tant que tel.

J’ai utilisé VC ++ Express 2010 et MinGW pour comstackr ce qui précède. Bien que VC ait donné l’avertissement

warning C4309: '=' : truncation of constant value

le résultat ne semble pas refléter cela.

PS Cela pourrait être marqué comme un doublon possible de Si un tampon d’octets doit être signé ou non signé char buffer? 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 de N3337,

Section 3.9 Types

2 Pour tout object (autre qu’un sous-object de classe de base) de type T inscriptible, que l’object contienne ou non une valeur valide de type T, les octets sous-jacents (1.7) composant l’object peuvent être copiés dans un tableau de caractères ou char non signé. Si le contenu du tableau de char ou de unsigned char est recopié dans l’object, l’object conservera ultérieurement sa valeur d’origine.

Compte tenu de ce qui précède et que mon exemple original était sur une machine Intel où char défaut est signed char , je ne suis toujours pas convaincu si les caractères unsigned char devraient être préférés au caractère.

Rien d’autre?

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

  • il n’a pas de bits de remplissage, c’est-à-dire que tous les bits de stockage consortingbuent à la valeur des données
  • aucune opération binary à partir d’une valeur de ce type, lorsqu’elle est reconvertie dans ce type, peut produire un dépassement de capacité, des représentations d’interruption ou un comportement indéfini
  • il peut alias d’autres types de données sans violer les “règles d’aliasing”, c’est-à-dire que l’access à ces mêmes données via un pointeur typé différemment sera garanti pour voir toutes les modifications

Si ce sont les propriétés d’un type de données “binary” que vous recherchez, vous devez utiliser définitivement le caractère unsigned char .

Pour la deuxième propriété, nous avons besoin d’un type unsigned . Pour toutes les conversions sont définies avec modulo arihmetic, ici modulo UCHAR_MAX+1 , 256 dans 99% des architectures. Toute conversion de valeurs plus larges en caractères unsigned char correspond simplement à une troncature vers 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, la conversion des valeurs qui ne lui correspondent pas est donc mal définie. char n’est pas fixé pour être signé ou non signé, mais sur une plateforme particulière sur laquelle votre code est porté, il peut être signé même s’il n’est pas signé sur le vôtre.

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

Bien que VC ait donné l’avertissement … troncature de la valeur constante

Il vous dit que vous essayez de stocker des littéraux int à l’intérieur de variables de caractère. Cela peut être lié à la signature: si vous essayez de stocker un entier avec la valeur> 0x7F dans un caractère signé, des choses inattendues peuvent se produire. Formellement, il s’agit d’un comportement indéfini dans C, même si, en pratique, vous obtiendrez une sortie étrange si vous tentez d’imprimer le résultat sous la forme d’un nombre entier stocké dans un caractère (signé).

Dans ce cas précis, l’avertissement ne devrait pas avoir d’importance.

MODIFIER :

Dans d’autres questions apparentées, le caractère non signé est mis en surbrillance car il s’agit du seul type de données (octet / plus petit) dont la spécification C garantit l’absence de remplissage.

En théorie, tous les types d’entiers, à l’exception des caractères non signés et des caractères signés, peuvent contenir des “bits de remplissage”, conformément à C11 6.2.6.2:

“Pour les types entiers non signés autres que les caractères non signés, les bits de la représentation d’object doivent être divisés en deux groupes: les bits de valeur et les bits de remplissage (il n’y a pas besoin de l’un de ces derniers).”

“Pour les types entiers signés, les bits de la représentation d’object doivent être divisés en trois groupes: les bits de valeur, les bits de remplissage et le bit de signe. Il ne doit pas y avoir de bits de remplissage; le caractère signé ne doit pas comporter de bits de remplissage.”

Le standard C est volontairement vague et flou, permettant ces bits de remplissage théoriques car:

  • Il permet différentes tables de symboles que les tables 8 bits standard.
  • Il permet une signature signée par l’implémentation et des formats d’entiers signés étranges tels que le complément ou “signe et magnitude”.
  • Un entier ne peut 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 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 des caractères non signés ou des caractères signés simplement pour éviter certains scénarios théoriques du standard C.

Vous rencontrerez la plupart de vos problèmes lors de la comparaison du contenu des octets individuels:

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

peut imprimer “mauvais”, car, selon votre compilateur, c [0] sera étendu à -1, ce qui n’est pas la même chose que 0xff

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

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

Si j’ajoute une opération de décalage de bits au code que vous avez écrit, j’aurai un comportement indéfini. La comparaison supplémentaire 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); 

En ce qui concerne l’avertissement lors de la compilation: si le caractère est signé, vous essayez d’atsortingbuer la valeur 0xf0, qui ne peut pas être représentée dans le caractère signé (entre -128 et +127), et sera donc convertie en une valeur signée (- 16).

Déclarer le caractère comme signé supprimera l’avertissement et il est toujours bon d’avoir une version propre sans aucun avertissement.

La signature de type char est définie par l’implémentation, donc à moins de traiter des données de caractère (une chaîne utilisant le jeu de caractères de la plate-forme – généralement ASCII), il est généralement préférable de spécifier explicitement la signature en utilisant signed char ou unsigned char .

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

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

Si vous faites des choses qui ne sont pas “correctes” au sens de la norme, vous vous fiez à un comportement indéfini. Votre compilateur peut le faire comme vous le souhaitez aujourd’hui, mais vous ne savez pas ce qu’il fait 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 de Debug / Release, etc. Dès que vous quittez le chemin sécurisé du standard, vous risquez de rencontrer des problèmes.

Eh bien, comment appelez-vous les “données binarys”? Ceci est un tas de bits, sans aucune signification qui leur est atsortingbuée par cette partie spécifique du logiciel qui les appelle “données binarys”. Quel est le type de données primitif le plus proche, ce qui transmet l’idée de l’absence de signification spécifique à l’un quelconque de ces bits? Je pense que les caractères ne sont pas unsigned char .

Est-il vraiment nécessaire d’utiliser des caractères non signés pour contenir des données binarys, comme dans certaines bibliothèques qui travaillent sur le codage de caractères ou les tampons binarys?

“vraiment” nécessaire? Non.

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

Votre exemple utilise printf, qui n’est pas sûr pour le type. En d’autres termes, printf prend ses informations de formatage à partir de la chaîne de format et non du type de données. Vous pourriez 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 c ++ iostreams, le résultat sera différent (en fonction de la signature de c).

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

Unsigned spécifie que le bit le plus significatif des données (pour le caractère non signé 8-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 “sign” représente les données, pas le signe des autres bits).