Quelles sont les meilleures pratiques concernant les inns non signés?

43

J'utilise des fichiers non signés partout et je ne suis pas sûr de devoir le faire. Cela peut être des colonnes id primaire de la base de données aux compteurs, etc. Si un nombre ne doit jamais être négatif, j'utiliserai toujours un unsigned int.

Cependant, je remarque dans le code de quelqu'un d'autre que personne ne semble le faire. Y a-t-il quelque chose de crucial que je néglige?

Edit: Depuis cette question, j'ai également remarqué qu'en C, renvoyer des valeurs négatives pour les erreurs était chose courante plutôt que de lancer des exceptions comme en C ++.

wting
la source
26
Il suffit de faire attention for(unsigned int n = 10; n >= 0; n --)(boucles infiniment)
Chris Burt-Brown
3
En C et C ++, les ints non signés ont défini avec précision le comportement de débordement (modulo 2 ^ n). Les documents signés ne le sont pas. Les optimiseurs exploitent de plus en plus ce comportement de débordement non défini, conduisant à des résultats surprenants dans certains cas.
Steve314
2
Bonne question! J’ai moi aussi été tenté d’utiliser des allusions pour restreindre la plage, mais j’ai constaté que le risque / inconvénient était supérieur à tout avantage / commodité. Comme vous l'avez dit, la plupart des bibliothèques acceptent les intrusions habituelles. Cela rend difficile le travail avec, mais soulève également la question: est-ce que cela en vaut la peine? En pratique (en supposant que vous ne faites pas les choses de manière idiote), vous aurez rarement une valeur de -218 pour une valeur positive attendue. Ce -218 doit venir de quelque part, non? et vous pouvez retracer son origine. Arrive rarement. Utilisez des assertions, des exceptions, des contrats de code pour vous aider.
Job
@ William Ting: S'il s'agit uniquement de C / C ++, vous devez ajouter les balises appropriées à votre question.
CesarGon
2
@Chris: Quelle est l'importance du problème de la boucle infinie dans la réalité? Je veux dire, si la version finale est publiée, le code n'a évidemment pas été testé. Même si vous avez besoin de quelques heures pour le déboguer la première fois que vous faites cette erreur, la deuxième fois, vous devez savoir quoi rechercher en premier lorsque votre code ne s'arrête pas de se mettre en boucle.
Sécurisé

Réponses:

28

Y a-t-il quelque chose de crucial que je néglige?

Lorsque les calculs impliquent des types signés et non signés, ainsi que des tailles différentes, les règles de promotion du type peuvent être complexes et conduire à un comportement inattendu .

Je crois que c’est la principale raison pour laquelle Java a omis les types unsigned int.

Michael Borgwardt
la source
3
Une autre solution consisterait à vous demander de composer manuellement vos numéros, le cas échéant. C’est ce que Go semble faire (je n’en ai cependant joué qu’un petit peu), et j’aime mieux que l’approche de Java.
Tikhon Jelvis
2
C’était une bonne raison pour que Java n’inclue pas le type non signé 64 bits et peut-être une bonne raison de ne pas inclure un type non signé 32 bits [bien que la sémantique de l’ajout de valeurs 32 bits signées et non signées ne soit pas difficile-- une telle opération devrait simplement donner un résultat signé de 64 bits]. Les types non signés plus petits que intne poseraient pas cette difficulté, cependant (puisque tous les calculs favoriseront int); Je n'ai rien de bon à dire sur l'absence d'un type d'octets non signés.
Supercat
17

Je pense que Michael a un argument valable, mais pour l’OMI, tout le monde utilise int tout le temps (surtout for (int i = 0; i < max, i++), c’est parce que nous l’avons appris ainsi. Lorsque chaque exemple d'un livre " apprendre à programmer " est utilisé inten forboucle, très peu de gens vont jamais remettre en question cette pratique.

L'autre raison est qu'il intest 25% plus court que uint, et nous sommes tous paresseux ... ;-)

Treb
la source
2
Je suis d'accord avec le problème éducatif. La plupart des gens semblent ne jamais remettre en question ce qu'ils lisent: si c'est dans un livre, ça ne peut pas être faux, non?
Matthieu M.
1
C’est probablement aussi la raison pour laquelle tout le monde utilise postfix ++lors de l’incrémentation, malgré le fait que son comportement particulier est rarement nécessaire et peut même conduire à un retournement inutile des copies si l’index de la boucle est un itérateur ou un autre type non fondamental (ou si le compilateur est vraiment dense) .
underscore_d
Juste ne faites pas quelque chose comme "pour (uint i = 10; i> = 0; --i)". Utiliser uniquement ints pour les variables de boucle évite cette possibilité.
David Thornley
8

Le mélange de types signés et non signés peut vous plonger dans un monde de douleur. Et vous ne pouvez pas utiliser tous les types non signés, car vous rencontrerez des objets ayant une plage valide comprenant des nombres négatifs ou ayant besoin d'une valeur pour indiquer une erreur, et -1 est le plus naturel. Le résultat net est donc que beaucoup de programmeurs utilisent tous les types d'entiers signés.

David Schwartz
la source
1
Il est peut-être préférable de ne pas mélanger des valeurs valides avec une indication d'erreur dans la même variable et d'utiliser des variables distinctes pour cela. Certes, la bibliothèque standard C ne donne pas un bon exemple ici.
Sécurisé le
7

Pour moi, les types sont beaucoup sur la communication. En utilisant explicitement un unsigned int, vous me dites que les valeurs signées ne sont pas des valeurs valides. Cela me permet d’ajouter des informations lors de la lecture de votre code en plus du nom de la variable. Idéalement, un type non anonyme m'en dirait plus, mais cela me donne plus d'informations que si vous aviez utilisé ints partout.

Malheureusement, tout le monde n’est pas très conscient de ce que son code communique, et c’est probablement la raison pour laquelle vous voyez des données partout, même si les valeurs ne sont au moins pas signées.

Daramarak
la source
4
Mais je pourrais vouloir limiter mes valeurs pour un mois à 1 à 12 seulement. Est-ce que j'utilise un autre type pour cela? Qu'en est-il d'un mois? Certaines langues permettent en fait de restreindre de telles valeurs. D'autres, tels que .Net / C #, fournissent des contrats de code. Bien sûr, les entiers non négatifs sont assez fréquents, mais la plupart des langages prenant en charge ce type ne prennent pas en charge de restrictions supplémentaires. Alors, faut-il utiliser un mélange d'uints et de vérification d'erreur, ou tout simplement procéder à une vérification d'erreur? La plupart des bibliothèques ne demandent pas où il serait judicieux d’en utiliser une, par conséquent l’utiliser et le casting peut être gênant.
Job
@Job Je dirais que vous devriez utiliser une sorte de restriction imposée par le compilateur / interprète sur vos mois. Cela vous donnera peut-être un peu de temps à mettre en place, mais pour l’avenir, vous aurez une restriction imposée qui évite les erreurs et communique beaucoup plus clairement ce que vous attendez. Prévenir les erreurs et faciliter la communication sont beaucoup plus importants que les inconvénients lors de la mise en œuvre.
daramarak
1
"Il se peut que je veuille limiter mes valeurs pour un mois à un nombre compris entre 1 et 12" Si vous avez un ensemble fini de valeurs, par exemple mois, vous devez utiliser un type d'énumération, et non des entiers bruts.
Josh Caswell
6

J'utilise unsigned inten C ++ pour les index de tableaux, principalement, et pour tout compteur qui commence à 0. Je pense qu'il est bon de dire explicitement "cette variable ne peut pas être négative".

quant_dev
la source
14
Vous devriez probablement utiliser size_t pour cela en c ++
JohnB
2
Je sais, je ne peux pas être dérangé.
quant_dev
3

Vous devez vous en préoccuper lorsque vous avez affaire à un entier qui pourrait atteindre ou dépasser les limites d'un entier signé. Étant donné que le maximum positif d'un entier 32 bits est 2 147 483 647, vous devez utiliser un entier non signé si vous savez qu'il ne sera jamais négatif et que b) pourrait atteindre 2 147 483 648. Dans la plupart des cas, y compris les clés de base de données et les compteurs, je ne m'approcherai jamais de ce type de chiffres. Je ne me préoccupe donc pas de me demander si le bit de signe est utilisé pour une valeur numérique ou pour indiquer le signe.

Je dirais: utilisez int sauf si vous savez que vous avez besoin d'un int non signé.

Joel Etherton
la source
2
Lorsque vous travaillez avec des valeurs pouvant atteindre les valeurs maximales, commencez par vérifier les opérations pour les débordements d'entiers, quel que soit le signe. Ces vérifications sont généralement plus faciles pour les types non signés, car la plupart des opérations ont des résultats bien définis sans comportement indéfini et défini par l'implémentation.
sécurité
3

C'est un compromis entre simplicité et fiabilité. Plus le nombre de bogues pouvant être détectés lors de la compilation est élevé, plus le logiciel est fiable. Différentes personnes et organisations sont sur différents points de ce spectre.

Si vous effectuez une programmation très fiable dans Ada, vous utilisez même différents types pour des variables telles que la distance en pieds par rapport à la distance en mètres, et le compilateur la signale si vous les attribuez accidentellement à l'autre. C'est parfait pour la programmation d'un missile guidé, mais excessif (jeu de mots) si vous validez un formulaire Web. Il n'y a pas nécessairement quelque chose qui cloche dans les deux cas, pourvu que cela réponde aux exigences.

Karl Bielefeldt
la source
2

Je suis enclin à être d'accord avec le raisonnement de Joel Etherton, mais j'arrive à la conclusion opposée. À mon avis, même si vous savez qu'il est peu probable que les nombres s'approchent des limites d'un type signé, si vous savez que des nombres négatifs ne se produiront pas, il y a très peu de raisons d'utiliser la variante signée d'un type.

Pour la même raison, j’ai, dans quelques instances choisies, utilisé BIGINT(entier 64 bits) plutôt que INTEGER(entier 32 bits) dans des tables SQL Server. La probabilité que les données atteignent la limite de 32 bits dans un délai raisonnable est minime, mais si cela se produisait, les conséquences dans certaines situations pourraient être assez dévastatrices. Assurez-vous de bien mapper les types entre les langues, sinon vous allez vous retrouver avec une étrange curiosité vraiment très loin dans le futur ...

Cela dit, certaines choses, telles que les valeurs de clé primaire de base de données, signées ou non signées, importent peu, car à moins de réparer manuellement des données erronées ou quelque chose du genre, vous ne traitez jamais directement avec la valeur; c'est un identifiant, rien de plus. Dans ces cas, la cohérence est probablement plus importante que le choix exact de la signature. Sinon, vous vous retrouvez avec des colonnes de clé étrangère signées et d'autres non signées, sans motif apparent - ou encore cette étrange curiosité.

un CVn
la source
Si vous travaillez avec des données extraites d'un système SAP, je vous recommande fortement BIGINT pour les champs d'ID (tels que CustomerNumber, ArticleNumber, etc.). Tant que personne n'utilise les chaînes alphanumériques comme identifiants, c'est ... soupir
Treb
1

Je recommanderais qu'en dehors des contextes de stockage et d'échange de données restreints en espace, on utilise généralement des types signés. Dans la plupart des cas où un entier signé 32 bits serait trop petit, mais qu'une valeur non signée 32 bits suffirait pour aujourd'hui, la valeur non signée 32 bits ne sera pas longue non plus.

Les temps principaux à utiliser pour les types non signés sont ceux qui assemblent plusieurs valeurs en valeurs plus grandes (par exemple, conversion de quatre octets en un nombre de 32 bits) ou en décomposant des valeurs plus grandes en valeurs plus petites (par exemple, en stockant un nombre de 32 bits sous forme de quatre octets). ), ou quand on a une quantité qui est supposée "se retourner" périodiquement et qu’il faut la gérer (pensez à un compteur de service public résidentiel; la plupart d’entre eux ont suffisamment de chiffres pour s’assurer qu’ils ne se renverseront pas entre les lectures si elles sont lues trois fois par an, mais pas suffisamment pour ne pas se retourner pendant la durée de vie utile du compteur). Les types non signés ont souvent assez d '"étrangeté" pour être utilisés uniquement dans les cas où leur sémantique est nécessaire.

supercat
la source
1
"Je recommanderais [...] généralement d'utiliser des types signés." Hm, vous avez oublié de mentionner les avantages des types signés et vous n’avez donné qu’une liste des cas dans lesquels utiliser des types non signés. "étrangeté" ? Bien que la plupart des opérations non signées aient un comportement et des résultats bien définis, vous entrez un comportement non défini et défini lors de l'utilisation de types signés (débordement, décalage de bit, ...). Vous avez une définition étrange de "étrangeté" ici.
Sécurisé le
1
@Secure: "l'étrangeté" à laquelle je me réfère concerne la sémantique des opérateurs de comparaison, en particulier dans les opérations impliquant des types mixtes signés et non signés. Vous avez raison de dire que le comportement des types signés n’est pas défini lorsqu’on utilise des valeurs suffisamment grandes pour déborder, mais le comportement des types non signés peut être surprenant même lorsqu’il s’agit de nombres relativement petits. Par exemple, (-3) + (1u) est supérieur à -1. De plus, certaines relations associatives mathématiques normales s'appliquant aux nombres ne s'appliquent pas aux non-signés. Par exemple, (ab)> c n'implique pas (ac)> b.
Supercat
1
@Secure: S'il est vrai que l'on ne peut pas toujours compter sur un tel comportement associatif avec de "grands" nombres signés, les comportements fonctionnent comme prévu lorsqu'on utilise des nombres "petits" par rapport au domaine des entiers signés. En revanche, la non-association susmentionnée pose problème avec les valeurs non signées "2 3 1". Incidemment, le fait que les comportements signés aient un comportement indéfini lorsqu'ils sont utilisés hors limites peut permettre d'améliorer la génération de code sur certaines plates-formes lors de l'utilisation de valeurs inférieures à la taille du mot natif.
Supercat
1
Si ces commentaires avaient été dans votre réponse en premier lieu, au lieu d’une recommandation et d’un «nom» sans donner de raisons, je ne l’aurais pas commentée. ;) Bien que je ne sois toujours pas d’accord avec le mot "étrangeté", c’est simplement la définition du type. Utilisez le bon outil pour le travail donné et, bien sûr, connaissez-le. Les types non signés ne sont pas le bon outil lorsque vous avez besoin de relations +/-. Il y a une raison pour laquelle size_test non signé et ptrdiff_test signé.
Sécurisé le
1
@Secure: Si l'on veut représenter une séquence de bits, les types non signés sont excellents; Je pense que nous sommes d'accord là-bas. Et sur certains micros, les types non signés peuvent être plus efficaces pour les quantités numériques. Ils sont également utiles dans les cas où les deltas représentent des quantités numériques, mais pas les valeurs réelles (par exemple, les numéros de séquence TCP). D'un autre côté, chaque fois que l'on soustrait des valeurs non signées, il faut se soucier des angles, même lorsque les nombres sont petits. de tels calculs avec des valeurs signées ne présentent que des cas de coin lorsque les nombres sont grands.
Supercat
1

J'utilise des ints non signés pour clarifier mon code et son intention. Une des choses que je fais pour me protéger contre les conversions implicites inattendues lorsque je fais du calcul avec des types signés et non signés consiste à utiliser un raccourci non signé (2 octets en général) pour mes variables non signées. Ceci est efficace pour plusieurs raisons:

  • Lorsque vous faites de l'arithmétique avec vos variables courtes non signées et vos littéraux (de type int) ou vos variables de type int, cela garantit que la variable non signée sera toujours promue en un int avant d'évaluer l'expression, car int a toujours un rang supérieur à court. . Cela évite tout comportement inattendu en arithmétique avec les types signés et non signés, en supposant que le résultat de l'expression s'inscrit dans un int signé, bien sûr.
  • La plupart du temps, les variables non signées que vous utilisez ne dépasseront pas la valeur maximale d'un raccourci non signé de 2 octets (65 535)

Le principe général est que le type de vos variables non signées doit avoir un rang inférieur à celui des variables signées afin de garantir la promotion du type signé. Vous n'aurez alors aucun comportement de débordement inattendu. Évidemment, vous ne pouvez pas vous en assurer tout le temps, mais (le plus), il est souvent faisable de le faire.

Par exemple, récemment, j'en ai eu quelques uns comme ceci:

const unsigned short cuint = 5;
for(unsigned short i=0; i<10; ++i)
{
    if((i-2)%cuint == 0)
    {
       //Do something
    }
}

Le littéral '2' est de type int. Si i était un unsigned int au lieu d'un unsigned short, alors, dans la sous-expression (i-2), 2 serait promu à un unsigned int (puisque unsigned int a une priorité plus élevée que signé). Si i = 0, la sous-expression est égale à (0u-2u) = une valeur massive due au débordement. Même idée avec i = 1. Cependant, comme i est un court non signé, il est promu au même type que le littéral '2', qui est signé int, et tout fonctionne bien.

Pour plus de sécurité: dans les rares cas où l'architecture que vous implémentez fait int en entier 2 octets, les deux opérandes de l'expression arithmétique peuvent être promus en unsigned int dans le cas où la variable courte unsigned ne correspond pas. dans le 2-byte int signé, ce dernier a une valeur maximale de 32 767 <65 535. (Voir https://stackoverflow.com/questions/17832815/c-implicit-conversion-signed-unsigned pour plus de détails). Pour vous protéger contre cela, vous pouvez simplement ajouter un static_assert à votre programme comme suit:

static_assert(sizeof(int) == 4, "int must be 4 bytes");

et il ne compilera pas sur les architectures où int est égal à 2 octets.

Amiral adama
la source