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

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

Une des raisons pour lesquelles je suis au courant est qu’il est beaucoup plus logique d’utiliser des caractères s pour traiter des caractères plutôt que d’utiliser (u)int8_t s, comme mentionné dans cette question .

Mais si vous prévoyez de stocker un numéro, quand voudriez-vous utiliser un type dont vous ne savez pas quelle est sa taille? Ie Dans quelle situation voudriez-vous stocker un numéro dans un unsigned short sans savoir s’il s’agit de 8, 16 ou même 32 bits, au lieu d’utiliser un uint16t ?

Après cela, est-il préférable d’utiliser des entiers à largeur fixe, ou d’utiliser les types entiers normaux et de ne jamais supposer quoi que ce soit et d’utiliser sizeof où que vous ayez besoin de savoir combien d’octets ils utilisent?

    En fait, il est courant de stocker un numéro 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 pas dépasser 2 milliards, ou les faire respecter. Mais cela ne veut pas dire que j’ai besoin d’un type de 32 bits exact pour les stocker, tout type pouvant compter jusqu’à au moins 2 milliards me convient.

    Si vous essayez d’écrire du code très portable, vous devez garder à l’esprit que les types à largeur fixe sont tous facultatifs .

    Sur une implémentation C99 où CHAR_BIT est supérieur à 8 il n’y a pas d’ int8_t . La norme interdit son existence car il devrait avoir des bits de remplissage, et les types intN_t sont définis comme n’ayant pas de bits de remplissage (7.18.1.1/1). uint8_t donc aussi interdit car (merci, ouah) une implémentation n’est pas autorisée pour définir uint8_t sans int8_t .

    Donc, en code très portable, si vous avez besoin d’un type signé capable de contenir des valeurs allant jusqu’à 127, vous devez utiliser l’un des caractères signed char , int , int_least8_t ou int_fast8_t selon que vous voulez demander au compilateur de le faire:

    • travail en C89 ( signed char ou int )
    • éviter les promotions entières surprenantes dans les expressions arithmétiques ( int )
    • small ( int_least8_t ou signed char )
    • rapide ( int_fast8_t ou int )

    Il en va de même pour les types non signés, jusqu’à 255, avec les caractères unsigned char , unsigned int , uint_fast8_t et uint_fast8_t .

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

    En pratique, la plupart des gens n’ont jamais besoin d’écrire du code portable. À l’heure actuelle, CHAR_BIT > 8 ne CHAR_BIT > 8 que sur du matériel spécialisé et votre code général ne sera pas utilisé. Bien sûr, cela pourrait changer à l’avenir, mais si c’est le cas, je pense qu’il y a tellement de code qui fait des suppositions sur Posix et / ou Windows (qui garantissent tous deux CHAR_BIT == 8 ), que la non-portabilité de votre code sera une petite partie d’un gros effort de portage du code vers cette nouvelle plate-forme. Une telle implémentation devra probablement se soucier de la façon de se connecter à Internet (qui traite en octets), bien avant que cela ne vous préoccupe de savoir comment faire fonctionner votre code 🙂

    Si vous supposez que CHAR_BIT == 8 toute façon, je ne pense pas qu’il y ait une raison particulière pour éviter (u)int8_t si vous souhaitez que le code fonctionne dans C89. Même en C89, il n’est pas 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 exiger seulement que le type puisse contenir 255 , plutôt que d’exiger qu’il ne contienne pas 256 , vous pouvez aussi éviter la dépendance à CHAR_BIT == 8 .

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

    Par exemple, avec la déclaration uint32_t i; , le comportement de l’expression (i-1) > 5 lorsque i est nul variera selon que uint32_t est plus petit que int . Sur les systèmes où par exemple int est de 64 bits (et uint32_t est quelque chose comme long short ), la variable que i devrais être promu à int ; la soustraction et la comparaison seraient effectuées comme signé (-1 est inférieur à 5). Sur les systèmes où int est de 32 bits, la soustraction et la comparaison seraient effectuées comme des unsigned int (la soustraction donnerait un très grand nombre, qui est supérieur à cinq).

    Je ne sais pas combien de code repose sur le fait que des résultats intermédiaires d’expressions impliquant des types non signés sont nécessaires pour boucler même en l’absence de types de typage (IMHO, si le comportement d’emballage était souhaité, le programmeur aurait dû inclure un transtypage) (uint32_t)(i-1) > 5 ) mais la norme ne permet actuellement aucune marge de manœuvre. Je me demande quels problèmes se poseraient si une règle autorisant au moins un compilateur à promouvoir des opérandes sur un type entier plus long en l’absence de typographie ou de coercition de type [par ex. uint32_t i,j , une affectation comme j = (i+=1) >> 1; serait nécessaire pour couper le débordement, comme le ferait j = (uint32_t)(i+1) >> 1; , mais j = (i+1)>>1 ne serait pas]? Ou, à cet égard, à quel point il serait difficile pour les fabricants de compilateurs de garantir que toute expression de type intégral dont les résultats intermédiaires pourraient tous s’insérer dans le plus grand type signé et n’impliquant pas de décalages corrects par des quantités non constantes les résultats comme si tous les calculs avaient été effectués sur ce type? Il me semble plutôt icky que sur une machine où int est de 32 bits:

       uint64_t a, b, c;
       ...
       a & = ~ 0x40000000;
       b & = ~ 0x80000000;
       c & = ~ 0x100000000;
    

    efface un bit chacun de a et c , mais efface les 33 bits supérieurs de b ; la plupart des compilateurs ne donneront aucune indication que quelque chose est «différent» de la seconde expression.

    Il est vrai que la largeur d’un type d’entier standard peut changer d’une plate-forme à une autre mais pas sa largeur minimale.

    Par exemple, le standard C spécifie qu’un int est au moins de 16-bit et un long au moins de 32-bit .

    Si vous n’avez pas de contrainte de taille lors du stockage de vos objects, vous pouvez laisser cela à l’implémentation. Par exemple, si votre valeur maximale signée tient dans un 16-bit vous pouvez simplement utiliser un int . Vous laissez ensuite l’implémentation avoir le dernier mot de ce que c’est la largeur int naturelle de l’architecture ciblée par l’implémentation.

    Vous ne devez utiliser que les types de largeur fixes lorsque vous faites une hypothèse sur la largeur.

    uint8_t et unsigned char sont les mêmes sur la plupart des plates-formes, mais pas sur tous. L’utilisation de uint8_t met l’accent sur le fait que vous supposez une architecture avec 8 bits char et que cela ne se comstack pas sur d’autres, donc c’est une fonctionnalité.

    Sinon, j’utiliserais le typedef “sémantique” tel que size_t , uintptr_t , ptrdiff_t car ils reflètent beaucoup mieux ce que vous avez en tête avec les données. Je n’utilise presque jamais les types de base directement, int uniquement pour les retours d’erreur, et je ne me souviens pas avoir jamais utilisé de short .

    Edit: Après une lecture attentive de C11, je conclus que uint8_t , s’il existe, doit être un caractère unsigned char et ne peut pas être simplement un caractère char même si ce type est non signé. Cela provient de l’exigence de 7.20.1 p1 que tous les intN_t et uintN_t doivent être les types signés et non signés correspondants . La seule paire de ce type pour les types de caractères est le caractère signed char et le caractère unsigned char .

    Le code devrait révéler au 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é . La même chose vaut pour la taille. Est-il vraiment important pour l’algorithme qu’une variable soit par défaut 16 bits? Ou est-ce juste une microgestion inutile et une tentative infructueuse d’optimiser?

    C’est ce qui fait de la programmation un art – pour montrer ce qui est important.