J'ai une question très simple qui me perturbe depuis longtemps. Je traite des réseaux et des bases de données afin que de nombreuses données que je traite sont des compteurs de 32 bits et de 64 bits (non signés), ID d'identification 32 bits et 64 bits (ne disposent pas non plus de la cartographie significative pour le signe). Je ne suis pratiquement jamais géré avec une affaire de mot réelle qui pourrait être exprimée en tant que nombre négatif.
Mes collègues utilisent régulièrement des types non signés comme uint32_t
et uint64_t
Pour ces questions et parce que cela se produit si souvent, nous les utilisons également pour les indices de tableau et autres utilisations entier courantes.
En même temps, divers guides de codage que je lis (par exemple, Google) découragent l'utilisation des types d'entiers non signés, et autant que je ne connaisse ni Java ni Scala types d'entiers non signés.
Donc, je ne pouvais pas comprendre quelle est la bonne chose à faire: l'utilisation de valeurs signées dans notre environnement serait très gênante, au même moment, les guides codant pour insister pour faire exactement cela.
Il y a deux écoles de pensée à ce sujet et non plus jamais d'accord.
Le premier fait valoir qu'il existe des concepts qui sont intrinsèquement non signés - tels que des indices de réseau. Cela n'a aucun sens d'utiliser des numéros signés pour ceux qu'il peut entraîner des erreurs. Il peut également imposer des limites non nécessaires aux choses - un tableau qui utilise des index 32 bits signés ne peut accéder que 2 milliards d'entrées, tout en passant à un nombre non signé de 32 bits permettant de 4 milliards d'entrées.
La seconde affirme que dans tout programme qui utilise des chiffres non signés, plus tôt ou tard, vous finirez par faire des arithmétiques mixtes non signées. Cela peut donner des résultats étranges et inattendus: la mise en forme d'une grande valeur non signée à la signature donne un nombre négatif et de jeter avec inverse un nombre négatif sur un non signé donne un grand positif. Cela peut être une grosse source d'erreurs.
Tout d'abord, la ligne directrice de codage Google C++ n'est pas une très bonne à suivre: elle évite des objets comme des exceptions, boost, etc. qui sont des agrafes de modernes C++. Deuxièmement, juste parce qu'une certaine ligne directrice fonctionne pour la société X ne signifie pas que ce sera le bon ajustement pour vous. Je continuerais à utiliser des types non signés, car vous avez un bon besoin pour eux.
Une règle de pouce décente pour C++ est la suivante: préférez int
Sauf si vous avez une bonne raison d'utiliser quelque chose d'autre.
Les autres réponses manquent d'exemples de monde réels, donc je vais en ajouter un. Une des raisons pour lesquelles je (personnellement) essaie d'éviter les types non signés.
Pensez à utiliser Standard Taille_T comme indice de tableau:
for (size_t i = 0; i < n; ++i)
// do something here;
OK parfaitement normal. Ensuite, considérons que nous avons décidé de changer la direction de la boucle pour une raison quelconque:
for (size_t i = n - 1; i >= 0; --i)
// do something here;
Et maintenant ça ne marche pas. Si nous 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 que cela s'est passé en production et était difficile à déboguer.
Une autre raison pour moi sont des avertissements ennuyeux, ce qui vous fait écrire quelque chose comme ceci à chaque fois :
int n = 123; // for some reason n is signed
...
for (size_t i = 0; i < size_t(n); ++i)
Ce sont des choses mineures, mais elles s'additionnent. J'ai l'impression que le code est plus propre si seulement les entiers signés sont utilisés partout.
Edit : Bien sûr, les exemples ont l'air stupide, mais j'ai vu des gens faire cette erreur. S'il y a un moyen aussi simple de l'éviter, pourquoi ne pas l'utiliser?
Lorsque je compile la pièce de code suivante avec VS2015 ou GCC, je ne vois aucun avertissement avec les paramètres d'avertissement par défaut (même avec -wall pour GCC). Vous devez demander -wextra d'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 l'analyseur statique), mais dans de nombreux projets de vie réels, les gens ne le font pas.
#include <vector>
#include <iostream>
void unsignedTest()
{
std::vector<int> v{ 1, 2 };
for (int i = v.size() - 1; i >= 0; --i)
std::cout << v[i] << std::endl;
for (size_t i = v.size() - 1; i >= 0; --i)
std::cout << v[i] << std::endl;
}
int main()
{
unsignedTest();
return 0;
}
Les entiers non signés sont là pour une raison.
Considérez, par exemple, remettre des données comme des octets individuels, par exemple dans un paquet de réseau ou un tampon de fichier. Vous pouvez parfois rencontrer de telles bêtes comme des entiers 24 bits. Facilement décalé de trois entiers non signés 8 bits, pas si facile avec des entiers signés de 8 bits.
Ou pensez aux algorithmes à l'aide de tables de recherche de personnages. 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 supporte pas les entiers non signés? Vous auriez des indices négatifs à un tableau. Eh bien, je suppose que vous pourriez utiliser quelque chose comme charval + 128
Mais c'est juste laid.
De nombreux formats de fichiers utilisent en effet 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.
Ensuite, considérons 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é compte tellement que vous avez vraiment besoin de ce bit supplémentaire d'entiers non signés. Considérez par exemple les périphériques IOT qui sont expédiés en millions. Beaucoup de ressources de programmation peuvent ensuite être justifiées pour être dépensées sur des micro-optimisations.
Je dirais que la justification pour éviter d'utiliser des types d'entiers non signés (signe mixte arithmétique, des comparaisons de signes mixtes) peut être surmontée par un compilateur avec des avertissements appropriés. Ces avertissements ne sont généralement pas activés par défaut, mais voir par exemple. -Wextra
Ou séparément -Wsign-compare
(Auto-activé 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 activer ces avertissements du compilateur!
Il existe de nombreux cas où les entiers ne représentent pas réellement de nombres, mais par exemple un masque de bits, une carte d'identité, etc. Fondamentalement des cas où l'ajout de 1 à un entier n'a pas de résultat significatif. Dans ces cas, l'utilisation non signée.
Il y a beaucoup de cas où vous faites des arithmétiques avec des entiers. Dans ces cas, utilisez des entiers signés, pour éviter la mauvaise conduite autour de zéro. Voir de nombreux exemples avec des boucles, où l'exécution d'une boucle à zéro utilise un code très non intuitif ou est cassé en raison de l'utilisation de nombres non signés. Il y a l'argument "mais les indices ne sont jamais négatifs" - sûrs, 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 beau piège: dans une boucle "pour (i = 0; i <n; ++ i) a [i] ..." Si je suis non signé 32 bits, et la mémoire dépasse les adresses 32 bits, le compilateur ne peut pas optimiser L'accès à un [i] en incrémentation d'un pointeur, car à i = 2 ^ 32 - 1 i wraps autour. Même quand je ne reçois jamais aussi grand. L'utilisation d'entiers signés évite cela.