Quelques réponses ici mentionner le surprenant règles de promotion entre les entiers signés et non signés valeurs, mais cela semble plus comme un problème concernant le mélange signés et non signés, les valeurs, et ne pas forcément expliquer pourquoi signée est préférable au non signé, à l'extérieur de scénarios de mixage.
Dans mon expérience, en dehors de l'mixte des comparaisons et des règles de promotion, il ya deux principales raisons pour lesquelles des valeurs non signées sont de gros bug aimants.
Des valeurs non signées ont une discontinuité à zéro, le plus commun de la valeur dans la programmation
Les deux non signé et signé entiers ont un discontinuités à leurs valeurs minimales et maximales, où ils enrouler autour de la (non signé) ou provoquer un comportement non défini (signé). Pour unsigned
ces points sont à zéro et UINT_MAX
. Pour int
, ils sont à l' INT_MIN
et INT_MAX
. Des valeurs typiques de l' INT_MIN
et INT_MAX
sur le système avec 4 octets int
valeurs -2^31
et 2^31-1
, et sur un système de UINT_MAX
est typiquement 2^32-1
.
Le principal bug induisant problème avec unsigned
qui ne s'applique pas à l' int
, c'est qu'il a une discontinuité à zéro. Zéro, bien sûr, est un très commun de la valeur dans les programmes, ainsi que d'autres petites valeurs comme 1,2,3. Il est courant d'ajouter et de soustraire les petites valeurs, en particulier 1, dans différentes constructions, et si vous soustraire quoi que ce soit à partir d'un unsigned
de la valeur et il arrive à zéro, vous venez de recevoir une énorme valeur positive et un presque certain bug.
Considérons le code parcourt toutes les valeurs dans un vecteur par index à l'exception de la dernière0.5:
for (size_t i = 0; i < v.size() - 1; i++) { // do something }
Cela fonctionne très bien jusqu'à ce qu'un jour vous passez dans un vecteur vide. Au lieu de faire zéro itérations, vous bénéficiez d' v.size() - 1 == a giant number
1 , et vous ferez de 4 milliards d'itérations et presque ont une vulnérabilité de dépassement de tampon.
Vous devez l'écrire comme ceci:
for (size_t i = 0; i + 1 < v.size(); i++) { // do something }
De sorte qu'il peut être "fixe" dans ce cas, mais seulement par réfléchir à la unsigned la nature de l' size_t
. Parfois, vous ne pouvez pas appliquer le correctif ci-dessus, car au lieu d'une constante celui que vous avez quelques offset variable que vous souhaitez appliquer, ce qui peut être positif ou négatif: oui, de quel "côté" de la comparaison, il faut la mettre sur dépend de ce paramètre - maintenant, le code devient vraiment pénible.
Il y a un problème similaire avec le code qui tente d'itérer jusqu'à et y compris zéro. Quelque chose comme while (index-- > 0)
fonctionne très bien, mais la apparemment équivalentes while (--index >= 0)
ne seront jamais mettre fin à une valeur non signée. Votre compilateur peut vous avertir lorsque le côté droit est littérale de zéro, mais certainement pas si c'est une valeur déterminée au moment de l'exécution.
Contrepoint
Certains pourraient faire valoir que les valeurs signées ont également deux discontinuités, alors pourquoi s'en prendre à des non signé? La différence est que les deux discontinuités sont très (au maximum) loin de zéro. J'ai vraiment considérer cela comme un problème distinct de "dépassement", signé et non signé valeurs peuvent déborder à de très grandes valeurs. Dans de nombreux cas, le dépassement est impossible en raison de contraintes sur la gamme possible des valeurs, et tout débordement de nombreuses valeurs 64 bits peuvent être physiquement impossible). Même si possible, la possibilité d'un dépassement de problème est souvent infime par rapport à une "à zéro" bug, et de dépassement de capacité se produit pour des valeurs non signées trop. Donc, non signé combine le pire des deux mondes: potentiellement débordement avec une très grande amplitude des valeurs, et une discontinuité à zéro. Signé seulement a l'ancienne.
Beaucoup diront "vous perdez un peu" avec unsigned. C'est souvent vrai, mais pas toujours (si vous avez besoin de représenter les différences entre les valeurs non signées, vous allez perdre un peu de toute façon: tant de 32 bits, les choses sont limités à 2 Gio de toute façon, ou vous allez avoir une drôle de "zone grise" où dire un fichier peut être 4 Go, mais vous ne pouvez pas utiliser certaines Api sur le deuxième 2 GiB de la moitié).
Même dans le cas où unsigned vous achète un peu: il n'achète pas de vous: si vous aviez à l'appui de plus de 2 milliards de "choses", vous aurez probablement bientôt à l'appui de plus de 4 milliards de dollars.
Logiquement, les valeurs non signées sont un sous-ensemble de valeurs signées
Mathématiquement, les valeurs non signées (entiers non négatifs) sont un sous-ensemble des entiers signés (juste appelé _integers).2. Pourtant signé valeurs naturellement pop des opérations uniquement sur unsigned valeurs, telles que la soustraction. On peut dire que les valeurs non signées ne sont pas fermés en vertu de la soustraction. Ce n'est pas vrai de valeurs signées.
Voulez trouver le "delta" entre deux unsigned index dans un fichier? Bien mieux, vous faites la soustraction dans le bon ordre, sinon vous obtiendrez une réponse incorrecte. Bien sûr, vous avez souvent besoin d'un moment de l'exécution pour déterminer le bon ordre! Lorsque vous traitez avec des valeurs non signées par des nombres, vous trouverez souvent que (logiquement) les valeurs signées garder apparaissant de toute façon, alors vous pourriez aussi bien commencer avec ont signé.
Contrepoint
Comme mentionné dans la note (2) ci-dessus, les valeurs signées en C++ ne sont pas en fait un sous-ensemble de valeurs non signées de la même taille, de sorte que des valeurs non signées peuvent représenter le même nombre de résultats qui a signé valeurs.
Vrai, mais la gamme est moins utile. Envisager la soustraction, et non signés, les nombres de 0 à 2N, et des nombres signés avec une gamme de N à N. Arbitraire soustractions résultat de résultats dans la plage -à 2N 2N dans _both cas, et soit le type d'entier ne peut représenter que la moitié. Eh bien il s'avère que la région centrée autour de zéro de N à N est généralement beaucoup plus utile (contient plus de résultats réels dans le monde réel de code) que la gamme de 0 à 2N. Examiner toute typiques de la distribution autre qu'en uniforme (journal, zipfian, normal, quoi) et considérer les soustrayant les valeurs choisies au hasard à partir de la distribution: plus de façon valeurs dans [-N, N] de [0, 2N] (en effet, résultant de la distribution est toujours centré sur zéro).
64-bit ferme la porte à de nombreuses raisons pour utiliser des valeurs signées comme les numéros de
Je pense que les arguments ci-dessus ont déjà été convaincante pour des valeurs de 32 bits, mais le dépassement de cas, qui affectent à la fois signés et non signés, à des seuils différents, ne se pour des valeurs de 32 bits, depuis "2 milliards" est un nombre qui peut dépassé par de nombreux abstrait et grandeurs physiques (en milliards de dollars, des milliards de nanosecondes, des tableaux avec des milliards d'éléments). Donc, si quelqu'un est assez convaincu par le doublement de la plage positive des valeurs non signées, ils peuvent faire de la casse que le trop-plein est important, et il légèrement favorise non signé.
En dehors des domaines spécialisés 64 bits des valeurs largement sortir de cette préoccupation. Signé de 64 bits, les valeurs ont un haut de gamme de 9,223,372,036,854,775,807 - plus de neuf quintillion. C'est beaucoup de nanosecondes (environ 292 ans), et beaucoup d'argent. C'est aussi un ensemble plus grand que n'importe quel ordinateur est susceptible d'avoir de la RAM dans un souci de cohérence de l'espace d'adresse pour un long moment. Alors peut-être 9 quintillion est assez pour tout le monde (pour l'instant)?
Quand utiliser des valeurs non signées
Notez que le guide de style n'est pas d'interdire ou même nécessairement en décourager l'usage des nombres non signés. Il conclut avec:
Ne pas utiliser un unsigned type de borne à affirmer qu'une variable est non-négative.
En effet, il y a des bonnes utilise pour les variables:
-
Lorsque vous souhaitez traiter un N-quantité de bits non pas comme un entier, mais simplement un "sac de bits". Par exemple, comme un masque de bits ou bitmap, ou N valeurs booléennes ou quoi que ce soit. Cette utilisation va souvent de pair avec la largeur fixe de type uint32_t
et uint64_t
depuis que vous avez souvent envie de connaître la taille exacte de la variable. Un indicateur d'une variable particulière mérite ce traitement est que vous faites uniquement fonctionner sur elle avec avec le bit à bit les opérateurs tels que ~
, |
, &
, ^
, >>
et ainsi de suite, et pas avec les opérations arithmétiques telles que l' +
, -
, *
, /
etc.
Non signé est idéal ici parce que le comportement des opérateurs sur les bits est bien défini et standardisé. Signé valeurs ont plusieurs problèmes, tels que l'indéfini et indéterminé comportement lors du déplacement, et un nombre non précisé de la représentation.
- Lorsque vous voulez vraiment l'arithmétique modulaire. Parfois, vous voulez vraiment 2^N de l'arithmétique modulaire. Dans ces cas de "débordement" est une fonction, pas un bug. Des valeurs non signées de vous donner ce que vous voulez ici depuis qu'ils sont définis à l'utilisation de l'arithmétique modulaire. Signé valeurs ne peuvent pas être (facilement, efficacement) utilisé à tous car ils ont une quelconque représentation et de débordement n'est pas défini.
0.5 Après, j'ai écrit cela, j'ai réalisé ce est presque identique à Jarod de l'exemple, que je n'avais pas vu - et pour une bonne raison, c'est un bon exemple!
1 Nous parlons size_t
ici donc 2^32-1 sur un système 32 bits ou 2^64-1 sur un 64-bit.
2 En C++ ce n'est pas exactement le cas, parce que les valeurs non signées contiennent plus de valeurs à l'extrémité supérieure de la correspondante type signé, mais le problème de base existe que la manipulation de valeurs non signées peuvent être la cause (logiquement) les valeurs signées, mais il n'y a pas de problème avec les valeurs signées (depuis signé des valeurs incluent déjà des valeurs non signées).