J'ai une question très simple qui me déroute depuis longtemps. Je traite avec des réseaux et des bases de données, donc beaucoup de données que je traite sont des compteurs 32 bits et 64 bits (non signés), des identifiants d'identification 32 bits et 64 bits (également sans mappage significatif pour le signe). Je ne suis pratiquement jamais confronté à une véritable question verbale qui pourrait être exprimée en nombre négatif.
Moi et mes collègues utilisons régulièrement des types non signés comme uint32_t
et uint64_t
pour ces questions et parce que cela arrive si souvent, nous les utilisons également pour les index de tableaux et d'autres utilisations d'entiers courantes.
En même temps, divers guides de codage que je lis (par exemple Google) découragent l'utilisation de types entiers non signés, et pour autant que je sache, ni Java ni Scala n'ont de types entiers non signés.
Donc, je ne pouvais pas comprendre quelle était la bonne chose à faire: utiliser des valeurs signées dans notre environnement serait très gênant, tout en codant les guides pour insister pour faire exactement cela.
la source
Réponses:
Il y a deux écoles de pensée à ce sujet, et aucune ne sera jamais d'accord.
Le premier fait valoir qu'il existe certains concepts qui sont intrinsèquement non signés - tels que les index de tableau. Cela n'a aucun sens d'utiliser des numéros signés pour ceux-ci car cela peut entraîner des erreurs. Il peut également imposer des limites inutiles aux choses - un tableau qui utilise des index 32 bits signés ne peut accéder qu'à 2 milliards d'entrées, tandis que le passage à des numéros 32 bits non signés autorise 4 milliards d'entrées.
La seconde fait valoir que dans tout programme utilisant des nombres non signés, vous finirez tôt ou tard par faire de l'arithmétique mixte signée-non signée. Cela peut donner des résultats étranges et inattendus: la conversion d'une grande valeur non signée en signé donne un nombre négatif et inversement la conversion d'un nombre négatif en non signé en donne un grand positif. Cela peut être une grande source d'erreurs.
la source
int
est plus court pour taper :)int
est plus que suffisant pour les index de tableau 99,99% des fois. Les problèmes arithmétiques signés - non signés sont beaucoup plus courants et ont donc priorité sur ce qu'il faut éviter. Oui, les compilateurs vous en avertissent, mais combien d'avertissements recevez-vous lors de la compilation d'un projet de taille importante? Ignorer les avertissements est dangereux et une mauvaise pratique, mais dans le monde réel ...size_t
, sauf s'il y a un cas spécial bonne raison sinon.Tout d'abord, la directive de codage de Google C ++ n'est pas très bonne à suivre: elle évite les choses comme les exceptions, le boost, etc. qui sont des agrafes du C ++ moderne. Deuxièmement, ce n'est pas parce qu'une certaine directive fonctionne pour l'entreprise X qu'elle vous conviendra. Je continuerais à utiliser des types non signés, car vous en avez un bon besoin.
Une règle d'or décente pour C ++ est: préférez,
int
sauf si vous avez une bonne raison d'utiliser autre chose.la source
return false
si cet invariant n'est pas établi. Ainsi, vous pouvez soit séparer les choses et utiliser les fonctions init pour vos objets, soit lancer unstd::runtime_error
, laisser le déroulement de la pile se produire et laisser tous vos objets RAII se nettoyer automatiquement et vous, le développeur, pouvez gérer l'exception là où cela est pratique pour vous de le faire.nullptr
? retourner un objet "par défaut" (quoi que cela puisse signifier)? Vous n'avez rien résolu - vous venez de cacher le problème sous un tapis, et j'espère que personne ne le découvrira.signal(6)
? Si vous utilisez une exception, les 50% de développeurs qui savent comment les gérer peuvent écrire du bon code, et le reste peut être porté par leurs pairs.Les autres réponses manquent d'exemples réels, je vais donc en ajouter un. L'une des raisons pour lesquelles j'essaie (personnellement) d'éviter les types non signés.
Pensez à utiliser size_t standard comme index de tableau:
Ok, parfaitement normal. Ensuite, considérons que nous avons décidé de changer la direction de la boucle pour une raison quelconque:
Et maintenant ça ne marche pas. Si nous l'utilisions
int
comme itérateur, il n'y aurait aucun problème. J'ai vu une telle erreur deux fois au cours des deux dernières années. Une fois arrivé en production et difficile à déboguer.Une autre raison pour moi est les avertissements ennuyeux, qui vous font écrire quelque chose comme ça à chaque fois :
Ce sont des choses mineures, mais elles s'additionnent. J'ai l'impression que le code est plus propre si seuls des entiers signés sont utilisés partout.
Edit: Bien sûr, les exemples semblent stupides, mais j'ai vu des gens faire cette erreur. S'il existe un moyen si simple de l'éviter, pourquoi ne pas l'utiliser?
Lorsque je compile le morceau de code suivant avec VS2015 ou GCC, je ne vois aucun avertissement avec des paramètres d'avertissement par défaut (même avec -Wall pour GCC). Vous devez demander -Wextra pour obtenir un avertissement à ce sujet dans GCC. C'est l'une des raisons pour lesquelles vous devez toujours compiler avec Wall et Wextra (et utiliser un analyseur statique), mais dans de nombreux projets réels, les gens ne le font pas.
la source
for (size_t i = n - 1; i < n; --i)
cela fonctionnait bien.size_t
en sens inverse, il existe une directive de codage dans le style defor (size_t revind = 0u; revind < n; ++revind) { size_t ind = n - 1u - revind; func(ind); }
int
? :)int
taille suffisante pour contenir toutes les valeurs valides desize_t
. En particulier,int
peut autoriser les nombres uniquement jusqu'à 2 ^ 15-1, et le fait généralement sur les systèmes qui ont des limites d'allocation de mémoire de 2 ^ 16 (ou dans certains cas encore plus).long
peut être un pari plus sûr, bien qu'il ne soit toujours pas garanti de fonctionner. Seulsize_t
est garanti de fonctionner sur toutes les plateformes et dans tous les cas.Le problème ici est que vous avez écrit la boucle de manière onclever conduisant au comportement erroné. La construction de la boucle est comme si les débutants l'enseignaient pour les types signés (ce qui est correct et correct), mais elle ne convient tout simplement pas aux valeurs non signées. Mais cela ne peut pas servir de contre-argument contre l'utilisation de types non signés, la tâche ici est simplement de bien faire votre boucle. Et cela peut facilement être corrigé pour fonctionner de manière fiable pour les types non signés comme ceci:
Ce changement inverse simplement la séquence de la comparaison et de l'opération de décrémentation et est à mon avis le moyen le plus efficace, le moins perturbant, le plus propre et le plus court pour gérer les compteurs non signés dans les boucles en arrière. Vous feriez la même chose (intuitivement) lorsque vous utilisez une boucle while:
Aucun débordement ne peut se produire, le cas d'un conteneur vide est couvert implicitement, comme dans la variante bien connue de la boucle de compteur signée, et le corps de la boucle peut rester inchangé par rapport à un compteur signé ou une boucle directe. Il vous suffit de vous habituer à la construction de boucle d'aspect quelque peu étrange au début. Mais après avoir vu cela une douzaine de fois, il n'y a plus rien d'intelligible.
Je serais chanceux si les cours pour débutants montraient non seulement la bonne boucle pour les types signés mais aussi pour les types non signés. Cela éviterait quelques erreurs qui devraient à mon humble avis être imputées aux développeurs involontaires au lieu de blâmer le type non signé.
HTH
la source
Les entiers non signés sont là pour une raison.
Considérez, par exemple, le transfert de données sous forme d'octets individuels, par exemple dans un paquet réseau ou un tampon de fichiers. Vous pouvez parfois rencontrer des bêtes telles que des entiers 24 bits. Décalage binaire aisé de trois entiers non signés 8 bits, pas si facile avec des entiers signés 8 bits.
Ou pensez aux algorithmes utilisant des tables de recherche de caractères. Si un caractère est un entier non signé 8 bits, vous pouvez indexer une table de recherche par une valeur de caractère. Cependant, que faites-vous si le langage de programmation ne prend pas en charge les entiers non signés? Vous auriez des index négatifs sur un tableau. Eh bien, je suppose que vous pourriez utiliser quelque chose comme
charval + 128
ça, mais c'est tout simplement moche.En fait, de nombreux formats de fichiers utilisent des entiers non signés et si le langage de programmation d'application ne prend pas en charge les entiers non signés, cela pourrait être un problème.
Considérez ensuite les numéros de séquence TCP. Si vous écrivez un code de traitement TCP, vous voudrez certainement utiliser des entiers non signés.
Parfois, l'efficacité est tellement importante que vous avez vraiment besoin de ce bit supplémentaire d'entiers non signés. Prenons par exemple les appareils IoT qui sont expédiés par millions. De nombreuses ressources de programmation peuvent alors être justifiées pour être dépensées en micro-optimisations.
Je dirais que la justification pour éviter d'utiliser des types entiers non signés (arithmétique de signe mixte, comparaisons de signe mixte) peut être surmontée par un compilateur avec des avertissements appropriés. De tels avertissements ne sont généralement pas activés par défaut, mais voir par exemple
-Wextra
ou séparément-Wsign-compare
(activé automatiquement en C par-Wextra
, bien que je ne pense pas qu'il soit activé automatiquement en C ++) et-Wsign-conversion
.Néanmoins, en cas de doute, utilisez un type signé. Plusieurs fois, c'est un choix qui fonctionne bien. Et activez ces avertissements du compilateur!
la source
Il existe de nombreux cas où les entiers ne représentent pas réellement des nombres, mais par exemple un masque de bits, un identifiant, etc. Fondamentalement, des cas où l'ajout de 1 à un entier n'a aucun résultat significatif. Dans ces cas, utilisez non signé.
Il existe de nombreux cas où vous faites de l'arithmétique avec des nombres entiers. Dans ces cas, utilisez des entiers signés pour éviter tout comportement incorrect autour de zéro. Voir de nombreux exemples de boucles, où l'exécution d'une boucle jusqu'à zéro utilise du code très intuitif ou est interrompue en raison de l'utilisation de nombres non signés. Il y a l'argument "mais les indices ne sont jamais négatifs" - bien sûr, mais les différences d'indices par exemple sont négatives.
Dans le cas très rare où les indices dépassent 2 ^ 31 mais pas 2 ^ 32, vous n'utilisez pas d'entiers non signés, vous utilisez des entiers 64 bits.
Enfin, un piège sympa: dans une boucle "pour (i = 0; i <n; ++ i) a [i] ..." si i n'est pas signé 32 bits et que la mémoire dépasse les adresses 32 bits, le compilateur ne peut pas optimiser l'accès à un [i] en incrémentant un pointeur, car à i = 2 ^ 32 - 1, je m'enroule. Même quand n ne devient jamais aussi gros. L'utilisation d'entiers signés évite cela.
la source
Enfin, j'ai trouvé une très bonne réponse ici: "Secure Programming Cookbook" par J.Viega et M.Messier ( http://shop.oreilly.com/product/9780596003944.do )
Problèmes de sécurité avec des entiers signés:
Il y a des problèmes avec les conversions signées <-> non signées, il est donc déconseillé d'utiliser le mixage.
la source