84 votes

L'utilisation d'un int non signé plutôt que signé est-elle plus susceptible de provoquer des bogues? Pourquoi?

Dans le Google C++ Guide de Style, sur le thème "des Entiers non signés", il est suggéré que

En raison de l'accident historique, la norme C++ utilise également des entiers non signés pour représenter la taille des conteneurs - de nombreux membres de l'organisme de normalisation croire que c'est une erreur, mais il est effectivement impossible de fixer à ce stade. Le fait que unsigned l'arithmétique n'est pas de modéliser le comportement d'un simple entier, mais est défini par la norme pour le modèle de l'arithmétique modulaire (emballage autour de débordement/underflow), signifie qu'une importante classe de bugs ne peuvent être diagnostiqués par le compilateur.

Quel est le problème avec l'arithmétique modulaire? N'est-ce pas le comportement attendu d'un unsigned int?

Ce genre de bugs (de classe) le guide de référence? Débordant de bugs?

Ne pas utiliser un unsigned type de borne à affirmer qu'une variable est non-négative.

L'une des raisons que je ne peux penser à de l'aide signé plus int unsigned int, c'est que si elle ne le débordement (négatif), il est plus facile à détecter.

75voto

BeeOnRope Points 3617

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 number1 , 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).

37voto

Jarod42 Points 15729

Comme indiqué, le mixage unsigned et signed pourrait conduire à un comportement inattendu (même si bien définie).

Supposons que vous voulez pour itérer sur tous les éléments du vecteur, sauf pour les cinq dernières, vous pourriez tort d'écrire:

for (int i = 0; i < v.size() - 5; ++i) { foo(v[i]); } // Incorrect
// for (int i = 0; i + 5 < v.size(); ++i) { foo(v[i]); } // Correct

Supposons v.size() < 5, puis, en v.size() est unsigned, s.size() - 5 serait un très grand nombre, et donc, i < v.size() - 5 serait true pour une plage de valeur de i. Et UB puis arrive rapidement (hors limite l'accès une fois i >= v.size())

Si v.size() serait de retour signé de la valeur, alors s.size() - 5 aurait été négative, et dans les cas ci-dessus, la condition serait faux immédiatement.

De l'autre côté, l'indice doit être compris entre [0; v.size()[ donc unsigned de sens. Signé a également sa propre question UB avec trop-plein ou de mise en œuvre définies par le comportement de décalage à droite d'un négatif signé nombre, mais de moins en moins fréquentes source de bug pour l'itération.

21voto

Chris Uzdavinis Points 2416

L'un des plus terrifiants des exemples d'une erreur, c'est quand vous MÉLANGEZ signés et non signés valeurs:

#include <iostream>
int main()  {
    auto qualifier = -1 < 1u ? "makes" : "does not make";
    std::cout << "The world " << qualifier << " sense" << std::endl;
}

La sortie:

Le monde n'a pas de sens

Sauf si vous avez une application standard, il est inévitable que vous allez retrouver avec soit dangereux de mixe entre signés et non signés (valeurs résultant des erreurs d'exécution) ou si vous montez les avertissements et les faire des erreurs de compilation, vous vous retrouvez avec beaucoup de static_casts dans votre code. C'est pourquoi il est préférable d'utiliser rigoureusement les entiers signés pour les types de mathématiques ou de la logique de comparaison. Utilisez uniquement des unsigned pour les masques de bits et les types représentant les bits.

La modélisation d'un type non signé basé sur le domaine de la valeurs de vos numéros est une Mauvaise Idée. La plupart des numéros sont plus proches de 0 que de 2 milliards de dollars, donc avec des types non signés, beaucoup de vos valeurs sont plus proches du bord de la plage valide. Pour aggraver les choses, la finale de la valeur peut être dans un célèbre plage positive, mais tout en évaluant les expressions, les valeurs intermédiaires peuvent underflow et si elles sont utilisées dans la forme intermédiaire peut être TRÈS mauvaises valeurs. Enfin, même si vos valeurs sont supposées être toujours positif, cela ne veut pas dire qu'ils ne pourront pas interagir avec d'autres variables qui peuvent être négatifs, et donc, vous vous retrouvez avec un forcé situation de mélanger les entiers signés et non signés, ce qui est le pire endroit pour être.

12voto

chux Points 13185

Pourquoi utiliser un unsigned int plus susceptibles de causer des bugs que d'utiliser un signé int?

À l'aide d'un unsigned type n'est pas plus susceptible de causer des bugs que l'utilisation d'une signée type avec certaines catégories de tâches.

Utiliser le bon outil pour le travail.

Quel est le problème avec l'arithmétique modulaire? N'est-ce pas le comportement attendu d'un unsigned int?
Pourquoi utiliser un unsigned int plus susceptibles de causer des bugs que d'utiliser un signé int?

Si la tâche si bien assorti: rien de mal. Non, pas la plus probable.

De sécurité, de cryptage et d'authentification de l'algorithme de compter sur unsigned modulaire de mathématiques.

La Compression/décompression des algorithmes de trop ainsi que les différents formats graphiques prestations et sont moins buggy avec unsigned mathématiques.

Tout moment bit à bit les opérateurs et les changements sont utilisés, la unsigned opérations ne pas les obtenir foiré avec le signe-questions d'extension de signé mathématiques.


Entier signé de mathématiques a une interface intuitive regarder et se sentir facilement compris par tous, y compris aux apprenants de codage. C/C++ n'a pas été ciblé à l'origine, ni aujourd'hui devrait être une intro de langue. Pour de codage rapide qui utilise des filets de sécurité de débordement, d'autres langues sont mieux adaptés. Pour lean code rapide, C suppose que les codeurs sait ce qu'ils font (ils ont de l'expérience).

Un écueil de l' signé de mathématiques d'aujourd'hui est omniprésente, la 32-bit int qui est avec beaucoup de problèmes est bien assez large pour les tâches courantes sans contrôle de la portée. Cela conduit à l'excès de confiance que le trop-plein n'est pas codé contre. Au lieu de cela, for (int i=0; i < n; i++) int len = strlen(s); est considéré comme acceptable, car l' n est assumé < INT_MAX et les chaînes de caractères ne sera jamais trop longtemps, plutôt que d'être pleinement protégés allaient dans le premier cas, ou à l'aide de size_t, unsigned ou même long long dans le 2ème.

C/C++ développé à une époque 16-bits et 32-bits int et le bit d'un entier non signé de 16 bits size_t offre est importante. L'Attention a été nécessaire en ce qui concerne les problèmes de débordement être c' int ou unsigned.

Avec 32 bits (ou plus) pour les applications de Google sur la non-bit 16 int/unsigned plates-formes, offre le manque d'attention à +/- débordement de l' int compte tenu de sa large gamme. Cela fait sens pour de telles applications pour encourager int sur unsigned. Pourtant, int mathématiques n'est pas bien protégé.

L'étroitesse de 16 bits int/unsigned préoccupations s'appliquent aujourd'hui avec une sélection d'applications embarquées.

Google lignes directrices s'appliquent bien pour le code d'écrire aujourd'hui. Ce n'est pas une ligne directrice définitive pour l'ensemble de la vaste étendue de la gamme de code C/C++.


L'une des raisons que je ne peux penser à de l'aide signé plus int unsigned int, c'est que si elle ne le débordement (négatif), il est plus facile à détecter.

En C/C++, signé int mathématiques débordement est un comportement indéfini et donc certainement plus facile à détecter que le comportement défini de unsigned mathématiques.


Comme @Chris Uzdavinis bien commenté, mixage signé et non signé est préférable de l'éviter par tous (surtout les débutants) et sinon codé avec soin en cas de besoin.

5voto

Tyler Durden Points 4349

J'ai une certaine expérience avec Google guide de style, AKA the Hitchhiker's Guide to Fou Directives de Mauvais Programmeurs Qui sont entrés dans la Société il Y a très Longtemps. Cette directive n'est qu'un exemple parmi des dizaines de noisette règles dans ce livre.

Des erreurs se produisent uniquement avec les types non signés si vous essayez de faire de l'arithmétique avec eux (voir Chris Uzdavinis exemple ci-dessus), en d'autres termes, si vous les utilisez comme des nombres. Unsigned les types ne sont pas destinés à être utilisés pour stocker des quantités numériques, ils sont conçus pour stocker des comtes tels que la taille des conteneurs, qui ne peut jamais être négatif, et qu'ils peuvent et doivent être utilisés à cette fin.

L'idée d'utiliser l'arithmétique types (comme les entiers signés) pour stocker des conteneurs de taille est idiot. Souhaitez-vous utiliser un double pour stocker la taille d'une liste, trop? Il y a des gens chez Google, le stockage des conteneurs de taille à l'aide de types arithmétiques et obligeant les autres à faire la même chose nous dit quelque chose sur la société. Une chose que je remarque sur ces préceptes, c'est que les plus idiots qu'ils sont, plus ils ont besoin d'être strict do-it-ou-vous-êtes-tiré des règles car sinon les gens avec le sens commun serait ignorer la règle.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X