Je suis tombé sur un point intéressant aujourd'hui dans une revue de Code Review . @Veedrac a recommandé dans cette réponse que les types de taille variable (par exemple int
et long
) soient remplacés par des types de taille fixe comme uint64_t
et uint32_t
. Citation des commentaires de cette réponse:
Les tailles de int et long (et donc les valeurs qu'elles peuvent contenir) dépendent de la plate-forme. D'un autre côté, int32_t a toujours une longueur de 32 bits. Utiliser int signifie simplement que votre code fonctionne différemment sur différentes plates-formes, ce qui n'est généralement pas ce que vous voulez.
Le raisonnement derrière la norme ne fixant pas les types courants est partiellement expliqué ici par @supercat. C a été écrit pour être portable à travers les architectures, contrairement à l'assemblage qui était habituellement utilisé pour la programmation des systèmes à l'époque.
Je pense que l'intention de conception était à l'origine que chaque type autre que int soit la plus petite chose qui puisse gérer des nombres de différentes tailles, et que ce soit la taille "polyvalente" la plus pratique qui puisse gérer +/- 32767.
Quant à moi, j'ai toujours utilisé int
et pas vraiment inquiété les alternatives. J'ai toujours pensé que c'est le type le plus performant, la fin de l'histoire. Le seul endroit où je pensais qu'une largeur fixe serait utile est lors du codage des données pour le stockage ou pour le transfert sur un réseau. J'ai rarement vu des types à largeur fixe dans le code écrit par d'autres non plus.
Suis-je coincé dans les années 70 ou y a-t-il vraiment une justification pour l'utiliser int
à l'ère de C99 et au-delà?
la source
Réponses:
Il existe un mythe commun et dangereux selon lequel des types comme les
uint32_t
programmeurs n'ont pas à se soucier de la taille deint
. Bien qu'il soit utile que le Comité des normes définisse un moyen de déclarer des entiers avec une sémantique indépendante de la machine, les types non signés comme lauint32_t
sémantique sont trop lâches pour permettre l'écriture du code d'une manière à la fois propre et portable; en outre, les types signés commeint32
ont une sémantique qui, pour de nombreuses applications, sont définis de manière inutile et précise et empêchent donc ce qui serait autrement des optimisations utiles.Considérez, par exemple:
Sur les machines où
int
soit ne peut pas contenir 4294967295, soit 18446744065119617025, la première fonction sera définie pour toutes les valeurs den
etexponent
, et son comportement ne sera pas affecté par la taille deint
; en outre, la norme n'exigera pas qu'elle produise un comportement différent sur les machines de n'importe quelle taille deint
Certaines valeurs den
etexponent
, cependant, l'amènera à invoquer un comportementint
indéfini sur les machines où 4294967295 est représentable en tant que mais pas 18446744065119617025.La deuxième fonction produira un comportement indéfini pour certaines valeurs de
n
etexponent
sur les machines quiint
ne peuvent pas contenir 4611686014132420609, mais produira un comportement défini pour toutes les valeurs den
etexponent
sur toutes les machines où elle le peut (les spécifications pourint32_t
impliquent que le comportement d'habillage en complément à deux sur les machines où il est plus petit queint
).Historiquement, même si la norme ne disait rien sur ce que les compilateurs devraient faire avec le
int
débordementupow
, les compilateurs auraient toujours produit le même comportement que s'ilsint
avaient été suffisamment grands pour ne pas déborder. Malheureusement, certains compilateurs plus récents peuvent chercher à «optimiser» les programmes en éliminant les comportements non requis par la norme.la source
pow
, rappelez-vous que ce code n'est qu'un exemple et ne répond pasexponent=0
!exponent=1
cela entraînera la multiplication de n par lui-même une fois, car la décrémentation est effectuée après la vérification, si l'incrémentation est effectuée avant la vérification ( ie --exponent), aucune multiplication ne sera effectuée et n lui-même sera retourné.N^(2^exponent)
, mais les calculs de la formeN^(2^exponent)
sont souvent utilisés dans le calcul des fonctions d'exponentiation, et l'exponentiation mod-4294967296 est utile pour des choses comme le calcul du hachage de la concaténation de deux chaînes dont les hachages sont connus.uint32_t
par 31 ne donnera jamais UB, mais l'efficacité façon de calculer 31 ^ N implique des calculs de 31 ^ (2 ^ N), ce qui seraint32_t
Parfois, avoir défini un débordement et parfois non, ce que vous semblez mentionner, semble d'une importance minime par rapport au fait qu'il me permet de raisonner pour empêcher le débordement en premier lieu. Et si vous voulez un débordement défini, il y a de fortes chances que vous souhaitiez que le résultat modulo ait une valeur fixe - vous utilisez donc des types à largeur fixe de toute façon.Pour les valeurs étroitement liées aux pointeurs (et donc à la quantité de mémoire adressable) telles que les tailles de mémoire tampon, les index de tableau et Windows '
lParam
, il est logique d'avoir un type entier avec une taille dépendante de l'architecture. Ainsi, les types de taille variable sont toujours utiles. Voilà pourquoi nous avons les typedefssize_t
,ptrdiff_t
,intptr_t
, etc. Ils ont à être typedefs car aucun des types entier intégré C besoin d' être de taille pointeur.Donc , la question est vraiment de savoir si
char
,short
,int
,long
etlong long
sont encore utiles.IME, il est encore courant que les programmes C et C ++ soient utilisés
int
pour la plupart des choses. Et la plupart du temps (c'est-à-dire lorsque vos chiffres sont dans la plage ± 32 767 et que vous n'avez pas d'exigences de performances rigoureuses), cela fonctionne très bien.Mais que se passe-t-il si vous devez travailler avec des nombres compris entre 17 et 32 bits (comme les populations des grandes villes)? Vous pourriez utiliser
int
, mais ce serait coder en dur une dépendance de plate-forme. Si vous souhaitez respecter strictement la norme, vous pouvez utiliserlong
ce qui est garanti être d'au moins 32 bits.Le problème est que la norme C ne spécifie aucune taille maximale pour un type entier. Il existe des implémentations sur lesquelles
long
est 64 bits, ce qui double votre utilisation de la mémoire. Et si ceslong
éléments se trouvent être des éléments d'un tableau contenant des millions d'éléments, vous allez battre la mémoire comme un fou.Donc, ni n'est un type approprié à utiliser ici si vous voulez que votre programme soit à la fois multi-plateforme et économe en mémoire. Entrez .
int
long
int_least32_t
long
, évitant les problèmes de troncature deint
int
, évitant le gaspillage de mémoire du 64 bitslong
.int
OTOH, supposons que vous n'ayez pas besoin de nombres ou de tableaux énormes mais que vous avez besoin de vitesse. Et
int
peut être assez grand sur toutes les plates-formes, mais ce n'est pas nécessairement le type le plus rapide: les systèmes 64 bits ont généralement encore 32 bitsint
. Mais vous pouvez utiliserint_fast16_t
et obtenir le type « plus rapide », que ce soitint
,long
oulong long
.Il existe donc des cas d'utilisation pratiques pour les types de
<stdint.h>
. Les types entiers standard ne signifient rien. Surtoutlong
, qui peut être de 32 ou 64 bits, et peut être ou ne pas être suffisamment grand pour contenir un pointeur, selon le caprice des rédacteurs du compilateur.la source
uint_least32_t
est que leurs interactions avec d'autres types sont encore plus faiblement spécifiées que celles deuint32_t
. À mon humble avis, la norme devrait définir des types tels queuwrap32_t
etunum32_t
, avec la sémantique que tout compilateur qui définit le typeuwrap32_t
, doit promouvoir en tant que type non signé dans essentiellement les mêmes cas qu'il serait promu s'ilint
était de 32 bits, et tout compilateur qui définit le typeunum32_t
doit s'assurer que les promotions arithmétiques de base le convertissent toujours en un type signé capable de conserver sa valeur.intN_t
etuintN_t
, et dont les comportements définis seraient cohérents avecintN_t
etuintN_t
, mais qui donneraient aux compilateurs une certaine liberté dans les valeurs de code de cas attribuées en dehors de leur plage [permettant une sémantique similaire à celles qui étaient peut-être destiné àuint_least32_t
, mais sans incertitudes comme si l'ajout d'unuint_least16_t
et d'unint32_t
donnerait un résultat signé ou usigné.