51 votes

Y a-t-il une raison de ne pas utiliser des types entiers à largeur fixe (par exemple uint8_t) ?

En supposant que vous utilisez un compilateur qui prend en charge C99 (ou même juste stdint.h), y a-t-il une raison de ne pas utiliser des types entiers de largeur fixe tels que uint8_t?

Une raison que je connais est qu'il est beaucoup plus logique d'utiliser des char lorsque vous traitez des caractères au lieu d'utiliser des (u)int8_t, comme mentionné dans cette question.

Mais si vous prévoyez de stocker un nombre, quand voudriez-vous utiliser un type dont vous ne connaissez pas la taille? Par exemple, dans quelle situation aimeriez-vous stocker un nombre dans un unsigned short sans savoir s'il est de 8, 16 ou même 32 bits, au lieu d'utiliser un uint16t?

En suivant cette logique, est-il considéré comme une meilleure pratique d'utiliser des entiers de largeur fixe, ou d'utiliser les types entiers normaux et de ne jamais rien supposer et d'utiliser sizeof partout où vous devez savoir combien d'octets ils utilisent?

40voto

Steve Jessop Points 166970

Il est en fait assez courant de stocker un nombre sans avoir besoin de connaître la taille exacte du type. Il y a beaucoup de quantités dans mes programmes que je peux raisonnablement supposer ne dépasseront pas 2 milliards, ou contraindre à ne pas le faire. Mais cela ne signifie pas que j'ai besoin d'un type exact de 32 bits pour les stocker, tout type pouvant aller jusqu'à au moins 2 milliards me convient.

Si vous essayez d'écrire un code très portable, vous devez garder à l'esprit que les types de largeur fixe sont tous optionnels.

Sur une implémentation C99 où CHAR_BIT est supérieur à 8, il n'y a pas de int8_t. La norme l'interdit d'exister car il aurait des bits de remplissage, et les types intN_t sont définis sans bits de remplissage (7.18.1.1/1). uint8_t est donc également interdit parce qu'une implémentation n'est pas autorisée à définir uint8_t sans int8_t.

Ainsi, dans un code très portable, si vous avez besoin d'un type signé capable de contenir des valeurs jusqu'à 127, vous devriez utiliser l'un des signed char, int, int_least8_t ou int_fast8_t selon que vous voulez demander au compilateur de le rendre :

  • fonctionner en C89 (signed char ou int)
  • éviter des promotions d'entiers surprenantes dans les expressions arithmétiques (int)
  • petit (int_least8_t ou signed char)
  • rapide (int_fast8_t ou int)

Il en va de même pour un type non signé jusqu'à 255, avec unsigned char, unsigned int, uint_least8_t et uint_fast8_t.

Si vous avez besoin d'arithmétique modulo-256 dans un code très portable, vous pouvez soit prendre le modulo vous-même, masquer des bits, ou jouer avec des champs de bits.

En pratique, la plupart des gens n'ont jamais besoin d'écrire du code aussi portable. Pour le moment, CHAR_BIT > 8 ne se produit que sur un matériel spécialisé, et votre code pour un usage général ne sera pas utilisé sur celui-ci. Bien sûr, cela pourrait changer à l'avenir, mais s'il le fait, je soupçonne qu'il y a tellement de code qui fait des hypothèses sur Posix et/ou Windows (qui garantissent que CHAR_BIT == 8), que gérer la non-portabilité de votre code sera une petite partie d'un grand effort pour porter du code vers cette nouvelle plate-forme. Toute implémentation de ce genre devra probablement se soucier de comment se connecter à l'internet (qui fonctionne en octets), bien avant de s'inquiéter de comment faire fonctionner votre code :-)

Si vous supposez de toute façon que CHAR_BIT == 8, je ne pense pas qu'il y ait de raison particulière d'éviter (u)int8_t sauf si vous voulez que le code fonctionne en C89. Même en C89, il n'est pas si difficile de trouver ou d'écrire une version de stdint.h pour une implémentation particulière. Mais si vous pouvez facilement écrire votre code pour ne nécessiter que le type puisse contenir 255, plutôt que d'exiger qu'il ne puisse pas contenir 256, alors vous feriez mieux d'éviter la dépendance de CHAR_BIT == 8.

12voto

supercat Points 25534

Un problème qui n'a pas encore été mentionné est que l'utilisation des types entiers de taille fixe signifie que les tailles des variables ne changeront pas si les compilateurs utilisent des tailles différentes pour int, long, et ainsi de suite, cela ne garantira pas nécessairement que le code se comportera de la même manière sur des machines avec différentes tailles d'entiers, même lorsque les tailles sont définies.

Par exemple, étant donné la déclaration uint32_t i;, le comportement de l'expression (i-1) > 5 lorsque i est zéro variera en fonction du fait qu'un uint32_t soit plus petit qu'un int. Sur des systèmes où par exemple int fait 64 bits (et que uint32_t est quelque chose comme long short), la variable i serait promue en int; la soustraction et la comparaison seraient effectuées en tant que signée (-1 est inférieur à 5). Sur des systèmes où int fait 32 bits, la soustraction et la comparaison seraient effectuées en tant que unsigned int (la soustraction donnerait un nombre très grand, qui est supérieur à cinq).

Je ne sais pas dans quelle mesure le code repose sur le fait que les résultats intermédiaires d'expressions impliquant des types non signés doivent se replier même en l'absence de conversions de type (selon moi, si un comportement de repli était souhaité, le programmeur aurait dû inclure une conversion de type) (uint32_t)(i-1) > 5) mais la norme actuelle ne permet aucune marge de manœuvre. Je me demande quels problèmes pourraient être posés si une règle permettait au moins à un compilateur de promouvoir les opérandes en un type entier plus long en l'absence de conversions de type ou de coercitions de type [par exemple, étant donné uint32_t i,j, une affectation comme j = (i+=1) >> 1; serait obligée de couper le débordement, tout comme j = (uint32_t)(i+1) >> 1;, mais j = (i+1)>>1 ne le serait pas]? Ou, d'ailleurs, à quel point ce serait difficile pour les fabricants de compilateurs de garantir que toute expression de type entier dont les résultats intermédiaires pourraient tous tenir dans le type signé le plus grand et ne comprenaient pas de décalages à droite par des quantités non constantes, donnerait les mêmes résultats que si tous les calculs étaient effectués sur ce type? Il me semble plutôt délicat que sur une machine où int fait 32 bits:

  uint64\_t a,b,c;
  ...
  a &= ~0x40000000;
  b &= ~0x80000000;
  c &= ~0x100000000;

dégage un bit de a et c, mais dégage les 33 bits les plus élevés de b; la plupart des compilateurs ne donneront aucun indice que quelque chose est 'différent' concernant la deuxième expression.

7voto

ouah Points 75311

Il est vrai que la largeur d'un type entier standard peut changer d'une plateforme à l'autre mais pas son minimum de largeur.

Par exemple, la norme C spécifie qu'un int a une largeur d'au moins 16 bits et qu'un long a une largeur d'au moins 32 bits.

Si vous n'avez pas de contrainte de taille lors du stockage de vos objets, vous pouvez laisser cela à l'implémentation. Par exemple, si votre valeur signée maximale tient dans un 16 bits, vous pouvez simplement utiliser un int. Vous laissez ensuite à l'implémentation le dernier mot sur quelle est la largeur naturelle du int pour l'architecture ciblée par l'implémentation.

4voto

Jens Gustedt Points 40410

Vous ne devez utiliser que les types à largeur fixe lorsque vous faites une supposition sur la largeur.

uint8_t et unsigned char sont les mêmes sur la plupart des plateformes, mais pas sur toutes. En utilisant uint8_t, vous mettez l'accent sur le fait que vous supposez une architecture avec un char de 8 bits et que cela ne se compilerait pas sur les autres, il s'agit donc d'une fonctionnalité.

Autrement, j'utiliserais les typedef "sémantiques" tels que size_t, uintptr_t, ptrdiff_t car ils reflètent mieux ce que vous avez en tête avec les données. Je n'utilise presque jamais les types de base directement, int seulement pour les retours d'erreur, et je ne me souviens pas avoir jamais utilisé short.

Édit: Après une lecture attentive de C11, je conclus que uint8_t, s'il existe, doit être unsigned char et ne peut pas être simplement char même si ce type est non signé. Cela vient de l'exigence au 7.20.1 p1 que tous les intN_t et uintN_t doivent être les types signés et non signés correspondants. Les seuls paires de ce type pour les types de caractères sont signed char et unsigned char.

3voto

Aki Suihkonen Points 9888

Le code devrait révéler à un lecteur occasionnel (et au programmeur lui-même) ce qui est important. Est-ce juste un entier ou un entier non signé ou même un entier signé. Il en va de même pour la taille. Est-il vraiment important pour l'algorithme qu'une variable soit par défaut sur 16 bits? Ou est-ce simplement une micromanagement inutile et une tentative ratée d'optimiser?

C'est ce qui rend la programmation un art -- montrer ce qui est important.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X