Un tampon d'octets doit-il être un caractère signé ou un caractère non signé ou simplement un tampon de caractères? Des différences entre C et C++?
Merci.
Un tampon d'octets doit-il être un caractère signé ou un caractère non signé ou simplement un tampon de caractères? Des différences entre C et C++?
Une différence mineure dans la façon dont la langue la traite. A énorme différence dans la façon dont la convention le traite.
char
= ASCII (ou UTF-8, mais la signature y fait obstacle) textuel dataunsigned char
= octetsigned char
= rarement utiliséEt il y a du code qui s'appuie sur une telle distinction. Il y a une semaine ou deux, j'ai rencontré un bogue où les données JPEG étaient corrompues parce qu'elles étaient transmises au char*
version de notre fonction d'encodage Base64 - qui a "utilement" remplacé tous les UTF-8 invalides dans la "chaîne". Passer à BYTE
aka unsigned char
était tout ce qu'il fallait pour y remédier.
Si vous avez l'intention de stocker des données binaires arbitraires, vous devez utiliser unsigned char
. Il s'agit du seul type de données garanti sans bit de remplissage par la norme C. Chaque autre type de données peut contenir des bits de remplissage dans sa représentation d'objet (c'est-à-dire celui qui contient tous les bits d'un objet, au lieu de seulement ceux qui déterminent une valeur). L'état des bits de remplissage n'est pas spécifié et n'est pas utilisé pour stocker des valeurs. Donc, si vous lisez en utilisant char
des données binaires, les choses seraient réduites à la plage de valeurs d'un caractère (en n'interprétant que les bits de valeur), mais il peut toujours y avoir des bits qui sont simplement ignorés mais qui sont toujours là et lu par memcpy
. Tout comme le remplissage des bits dans les objets structurels réels. Tapez unsigned char
est garanti de ne pas les contenir. Cela découle de 5.2.4.2.1/2
(C99 TC2, n1124 ici):
Si la valeur d'un objet de type char est traitée comme un entier signé lorsqu'il est utilisé dans une expression, la valeur de
CHAR_MIN
doit être le même que celui deSCHAR_MIN
et la valeur deCHAR_MAX
doit être le même que celui deSCHAR_MAX
. Sinon, la valeur deCHAR_MIN
doit être 0 et la valeur deCHAR_MAX
doit être le même que celui deUCHAR_MAX
. La valeurUCHAR_MAX
doit être égal à2^CHAR_BIT − 1
De la dernière phrase, il s'ensuit qu'il n'y a plus d'espace pour les bits de remplissage. Si vous utilisez char
comme type de votre tampon, vous avez également le problème des débordements: affectation explicite de n'importe quelle valeur à un tel élément qui est dans la plage de 8
bits - vous pouvez donc vous attendre à ce qu'une telle affectation soit correcte - mais pas dans la plage d'un char
, qui est CHAR_MIN
..CHAR_MAX
, une telle conversion déborde et provoque des résultats définis par l'implémentation, y compris l'augmentation des signaux.
Même si des problèmes concernant ce qui précède n'apparaîtraient probablement pas dans les implémentations réelles (ce serait une très mauvaise qualité d'implémentation), il est préférable d'utiliser le bon tapez depuis le début, ce qui est unsigned char
.
Pour les chaînes, cependant, le type de données choisi est char
, ce qui sera compris par les fonctions de chaîne et d'impression. En utilisant signed char
à ces fins me semble une mauvaise décision.
Pour plus d'informations, lisez this proposal
qui contient un correctif pour une prochaine version de la norme C qui nécessitera éventuellement signed char
pas de bits de remplissage non plus. Il est déjà incorporé dans le document de travail .
Ça dépend.
Si le tampon est destiné à contenir du texte, il est probablement judicieux de le déclarer comme un tableau de char
et de laisser la plate-forme décider pour vous s'il est signé ou non par défaut. Cela vous donnera le moins de mal à transmettre les données dans et hors de la bibliothèque d'exécution de l'implémentation, par exemple.
Si le tampon est destiné à contenir des données binaires, cela dépend de la façon dont vous comptez l'utiliser. Par exemple, si les données binaires sont en réalité un ensemble compact d'échantillons de données qui sont des mesures ADC à point fixe 8 bits signées, alors signed char
serait mieux.
Dans la plupart des cas réels, le tampon est juste cela, un tampon, et vous ne vous souciez pas vraiment des types des octets individuels parce que vous avez rempli le tampon dans une opération en bloc, et vous êtes sur le point de le transmettre à un analyseur pour interpréter la structure de données complexe et faire quelque chose d'utile. Dans ce cas, déclarez-le de la manière la plus simple.
S'il s'agit en fait d'un tampon de 8 octets, plutôt que d'une chaîne dans les paramètres régionaux par défaut de la machine, j'utiliserais uint8_t
. Non pas qu'il existe de nombreuses machines dans lesquelles un caractère n'est pas un octet (ou un octet un octet), mais faire la déclaration `` c'est un tampon d'octets '' plutôt que `` c'est une chaîne '' est souvent une documentation utile.
Vous devez utiliser soit char ou char non signé mais jamais char signé. La norme présente les éléments suivants en 3.9/2
Pour tout objet (autre qu'un sous-objet de classe de base) de type POD T, 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 non signés. 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.
Il vaut mieux le définir comme caractère non signé. En fait, le type BYTE Win32 est défini comme un caractère non signé. Il n'y a aucune différence entre C & C++ entre cela.
Pour une portabilité maximale, utilisez toujours un caractère non signé. Il y a quelques cas où cela pourrait entrer en jeu. Les données sérialisées partagées entre les systèmes avec différents types d'endian viennent immédiatement à l'esprit. Lorsque vous effectuez un masquage de décalage ou de bits, les valeurs en sont une autre.
Le choix de int8_t vs uint8_t est similaire à lorsque vous comparez un ptr à NULL.
D'un point de vue fonctionnel, la comparaison avec NULL équivaut à la comparaison avec 0 car NULL est une # définition pour 0.
Mais personnellement, du point de vue du style de codage, j'ai choisi de comparer mes pointeurs à NULL car le NULL #define connote la personne qui gère le code que vous recherchez pour un mauvais pointeur ...
CONTRE
quand quelqu'un voit une comparaison à 0, cela signifie que vous recherchez une valeur spécifique.
Pour la raison ci-dessus, j'utiliserais uint8_t.
Si vous récupérez un élément dans une variable plus large, il sera bien sûr étendu au signe ou non.
Devrais et devrais ... j'ai tendance à préférer non signé, car il semble plus "brut", moins invitant à dire "hé, c'est juste un tas de petits ints
", si je veux souligner la binaire des données.
Je ne pense pas avoir déjà utilisé un _ signed char
pour représenter un tampon d'octets.
Bien sûr, une troisième option consiste à représenter le tampon comme void *
autant que possible. De nombreuses fonctions d'E/S courantes fonctionnent avec void *
, donc parfois la décision du type d'entier à utiliser peut être entièrement encapsulée, ce qui est bien.
Il y a plusieurs années, j'ai eu un problème avec une application de console C++ qui imprimait des caractères colorés pour ASCII valeurs supérieures à 128 et cela a été résolu en passant de char à char non signé, mais je pense que cela avait été résoluble tout en garder le type de caractère aussi.
Pour l'instant, la plupart des fonctions C/C++ utilisent char et je comprends mieux les deux langages maintenant, donc j'utilise char dans la plupart des cas.
Ça te préoccupe vraiment? Si vous ne le faites pas, utilisez simplement la valeur par défaut (char) et n'encombrez pas votre code avec une question sans importance. Sinon, les futurs responsables se demanderont pourquoi vous avez utilisé signé (ou non signé). Rendez leur vie plus simple.