Pourquoi int plutôt que unsigned int utilisé pour C et C ++ pour les boucles?

C’est une question assez idiote mais pourquoi int communément utilisé au lieu de unsigned int lors de la définition d’une boucle for pour un tableau en C ou C ++?

 for(int i;i<arraySize;i++){} for(unsigned int i;i<arraySize;i++){} 

Je reconnais les avantages de l’utilisation de l’ int lorsque vous faites autre chose que l’indexation de la masortingce et les avantages d’un iterator lors de l’utilisation de conteneurs C ++. Est-ce juste parce que cela n’a pas d’importance lors de la lecture en boucle d’un tableau? Ou devrais-je éviter tout cela ensemble et utiliser un type différent tel que size_t ?

C’est un phénomène plus général, souvent les gens n’utilisent pas les types corrects pour leurs nombres entiers. Le C moderne possède des typedefs sémantiques nettement supérieurs aux types entiers primitifs. Par exemple, tout ce qui est une “taille” doit simplement être saisi en tant que size_t . Si vous utilisez systématiquement les types sémantiques pour vos variables d’application, les variables de boucle sont également beaucoup plus faciles avec ces types.

Et j’ai vu plusieurs bogues qui étaient difficiles à détecter et qui provenaient de l’utilisation de l’ int ou de int . Code qui soudainement s’est écrasé sur de grandes masortingces et des trucs comme ça. Le simple codage correct avec des types corrects évite cela.

Utiliser int est plus correct d’un sharepoint vue logique pour indexer un tableau.

unsigned sémantique unsigned en C et C ++ ne signifie pas vraiment “non négatif” mais plutôt “masque de bits” ou “nombre entier modulo”.

Pour comprendre pourquoi unsigned n’est pas un bon type pour un nombre “non négatif”, veuillez prendre en compte

  • L’ajout d’un entier éventuellement négatif à un entier non négatif donne un entier non négatif
  • La différence de deux entiers non négatifs est toujours un entier non négatif
  • En multipliant un entier non négatif par un entier négatif, vous obtenez un résultat non négatif

Evidemment, aucune des phrases ci-dessus n’a de sens … mais c’est comme ça que fonctionne la sémantique unsigned C et C ++.

En fait, utiliser un type unsigned pour la taille des conteneurs est une erreur de conception de C ++ et, malheureusement, nous sums maintenant condamnés à utiliser ce mauvais choix pour toujours (pour la compatibilité descendante). Vous pouvez aimer le nom “unsigned” car il est similaire à “non-négatif” mais le nom n’est pas pertinent et ce qui compte c’est la sémantique … et unsigned est très loin de “non-négatif”.

Pour cette raison, lorsque vous codez la plupart des boucles sur des vecteurs, ma forme préférée est:

 for (int i=0,n=v.size(); i 

(bien sûr en supposant que la taille du vecteur ne change pas pendant l'itération et que j'ai réellement besoin de l'index dans le corps, sinon le for (auto& x : v)... est meilleur).

Cette fugue de unsigned dès que possible et utilisant des entiers simples a l'avantage d'éviter les pièges qui sont la conséquence d' unsigned size_t erreur de conception unsigned size_t . Par exemple, considérez:

 // draw lines connecting the dots for (size_t i=0; i 

le code ci-dessus aura des problèmes si le vecteur pts est vide car pts.size()-1 est un nombre énorme de non-sens dans ce cas. Traiter des expressions où a < b-1 n'est pas la même chose que a+1 < b même pour les valeurs les plus courantes, c'est comme danser dans un champ de mines.

Historiquement, la justification de size_t unsigned est de pouvoir utiliser le bit supplémentaire pour les valeurs, par exemple être capable d'avoir 65535 éléments dans les tableaux au lieu de 32767 sur les plates-formes 16 bits. A mon avis, même à ce moment-là, le coût supplémentaire de ce mauvais choix sémantique ne valait pas le coup (et si 32767 éléments ne suffisent plus maintenant, 65535 ne suffiront pas longtemps).

Les valeurs non signées sont excellentes et très utiles, mais PAS pour représenter la taille du conteneur ou pour les index; pour la taille et l'index, les entiers signés réguliers fonctionnent beaucoup mieux car la sémantique est ce à quoi vous vous attendez.

Les valeurs non signées sont le type idéal lorsque vous avez besoin de la propriété arithmétique modulo ou lorsque vous souhaitez travailler au niveau bit.

Pas beaucoup de différence. Un avantage de int est sa signature. Donc, int i < 0 est logique, alors que unsigned i < 0 n'a pas grand chose.

Si les index sont calculés, cela peut être bénéfique (par exemple, vous pouvez obtenir des cas où vous n'entrerez jamais dans une boucle si un résultat est négatif).

Et oui, c'est moins écrit :-)

C’est de la paresse et de l’ignorance. Vous devez toujours utiliser les bons types pour les index, et à moins que vous ne size_t informations supplémentaires limitant la plage d’indices possibles, size_t est le bon type.

Bien sûr, si la dimension était lue à partir d’un champ à un octet dans un fichier, vous savez qu’elle est comprise entre 0 et 255, et int serait un type d’index parfaitement raisonnable. De même, int serait correct si vous bouclez un nombre de fois fixe, comme 0 à 99. Mais il y a encore une autre raison de ne pas utiliser int : si vous utilisez i%2 dans votre corps de boucle pour traiter différemment les indices pairs / impairs, i%2 est beaucoup plus cher quand i signé que quand i ne i pas signé …

Utiliser int pour indexer un tableau est hérité, mais toujours largement adopté. int est juste un type de nombre générique et ne correspond pas aux capacités d’adressage de la plateforme. Dans le cas où il est plus court ou plus long que cela, vous pouvez rencontrer des résultats étranges lorsque vous essayez d’indexer un très grand tableau qui va au-delà.

Sur les plateformes modernes, off_t , ptrdiff_t et size_t garantissent beaucoup plus de portabilité.

Un autre avantage de ces types est qu’ils donnent un contexte à quelqu’un qui lit le code. Lorsque vous voyez les types ci-dessus, vous savez que le code effectuera un indice de tableau ou une arithmétique de pointeur, et non pas n’importe quel calcul.

Donc, si vous voulez écrire du code à l’épreuve des balles, portable et contextuel, vous pouvez le faire au prix de quelques frappes.

GCC prend même en charge une extension de type qui vous évite de taper le même nom de fichier partout:

 typeof(arraySize) i; for (i = 0; i < arraySize; i++) { ... } 

Ensuite, si vous modifiez le type de arraySize , le type de i change automatiquement.

J’utilise int car il nécessite moins de saisie physique et cela n’a pas d’importance – ils occupent la même quantité d’espace, et à moins que votre tableau ne contienne quelques milliards d’éléments, vous ne déborderez pas si vous n’utilisez pas un compilateur 16 bits , ce que je ne suis généralement pas.

Cela dépend vraiment du codeur. Certains codeurs préfèrent le perfectionnisme de type, donc ils utiliseront n’importe quel type de comparaison. Par exemple, s’ils parcourent une chaîne C, vous pouvez voir:

 size_t sz = strlen("hello"); for (size_t i = 0; i < sz; i++) { ... } 

Alors que s'ils ne font que quelque chose 10 fois, vous verrez probablement encore int :

 for (int i = 0; i < 10; i++) { ... } 

Parce que, sauf si vous avez un tableau de taille supérieure à deux gigaoctets de type char , ou 4 gigaoctets de type short ou 8 gigaoctets de type int etc., peu importe si la variable est signée ou non.

Alors, pourquoi taper plus quand vous pouvez taper moins?

Outre le fait qu’il est plus court à taper, la raison en est que cela permet des nombres négatifs.

Comme nous ne pouvons pas dire à l’avance si une valeur peut être négative, la plupart des fonctions qui prennent des arguments entiers prennent la variété signée. Comme la plupart des fonctions utilisent des entiers signés, il est souvent moins pratique d’utiliser des entiers signés pour des choses comme les boucles. Sinon, vous avez la possibilité d’append un tas de caractères.

Au fur et à mesure que nous passons à des plates-formes 64 bits, la plage non signée d’un entier signé doit être plus que suffisante dans la plupart des cas. Dans ces cas, il n’y a pas beaucoup de raison de ne pas utiliser un entier signé.

Prenons l’exemple simple suivant:

 int max = some_user_input; // or some_calculation_result for(unsigned int i = 0; i < max; ++i) do_something; 

Si max s'avère être une valeur négative, disons -1, le -1 sera considéré comme UINT_MAX (lorsque deux entiers avec le rang sam mais une signature différente sont comparés, le signé sera traité comme un non signé). Par contre, le code suivant n'aurait pas ce problème:

 int max = some_user_input; for(int i = 0; i < max; ++i) do_something; 

Donnez une entrée max négative, la boucle sera sautée en toute sécurité.