Conversions entières (rétrécissement, élargissement), comportement indéfini

Il était assez difficile pour moi de trouver des informations sur ce sujet d’une manière que je puisse facilement comprendre, alors je demande un examen de ce que j’ai trouvé.


Dans les exemples, je ferai référence à:

(signed/unsigned) int bigger; (signed/unsigned) char smaller; 
  1. Tronquer des entiers. (plus gros-> plus petit)

    • d’abord tronquer bigger côté MSB pour correspondre à smaller taille smaller petite.
    • deuxièmement, convertir le résultat tronqué en signé / non signé en fonction du type plus petit.

    Si une valeur plus grande est trop grande pour tenir dans un type plus petit, cela entraîne un comportement indéfini (corrigez-moi à ce sujet). Cependant, ma règle devrait fonctionner sur toutes les machines (corrigez-moi également) et les résultats devraient être prévisibles.

  2. Elargissement des nombres entiers (plus petit -> plus grand)

    a) caractère signed char -> signed int

    • préférez plus petit avec MSB (1 ou 0) pour correspondre à une plus grande taille
    • convertir en signé

    b) caractère signed char -> unsigned int

    • préférez plus petit avec MSB (1 ou 0) pour correspondre à une plus grande taille.
    • convertir en non signé

    c) caractère unsigned char -> signed int

    • append des 0 pour correspondre à une plus grande taille
    • convertir en signé

    d) unsigned char -> unsigned int

    • append des 0 pour correspondre à une plus grande taille
    • convertir en non signé

Où sont les comportements indéfinis / non spécifiés que je n’ai pas mentionnés qui pourraient apparaître?

Une conversion intégrale ne produit jamais de comportement indéfini (elle peut produire un comportement défini par l’implémentation).

Une conversion en un type pouvant représenter la valeur en cours de conversion est toujours bien définie: la valeur rest simplement inchangée.

Une conversion en un type non signé est toujours bien définie: la valeur est prise modulo UINT_MAX + 1 (ou quelle que soit la valeur maximale admise par le type cible).

Une conversion en un type signé qui ne peut pas représenter la valeur en cours de conversion entraîne une valeur définie par l’implémentation ou un signal défini par l’implémentation.

Notez que les règles ci-dessus sont définies en termes de valeurs entières et non en termes de séquences de bits.

Du document standard C (p.50 version préliminaire 201x je crois et citation non exacte):

  • Deux nombres entiers signés ne doivent pas avoir le même rang

  • Le rang du nombre entier signé doit être supérieur au rang de tout nombre entier signé avec moins de précision.

  • long long int est supérieur à long int qui est supérieur à int qui est supérieur à short int qui est supérieur à char signé.

  • signés et non signés de même précision ont le même rang (ex: signed int est le même rang que unsigned int)

  • Le rang de tout type entier standard doit être supérieur à celui de tout type entier étendu de même largeur.

  • Le rang de char est égal à unsigned char est égal à signé char.

(Je laisse tomber bool parce que vous les avez exclus de votre question)

  • Le rang de tout entier signé étendu par rapport à un autre entier signé étendu est défini par l’implémentation mais rest soumis à d’autres règles de classement de conversion d’entier.

  • pour tous les types entiers T1 T2 et T3, T1 a un rang supérieur à T2 et T2 a un rang supérieur à T3, que T1 a un rang supérieur à T3.

Un object de type entier (autre que int et signé int) dont le rang entier est inférieur à ou égal à EQUAL au rang entier int et unsigned int, un champ binary de type _Bool, int, signé int ou unsigned int; si un int peut représenter toutes les valeurs du type d’origine, la valeur est convertie en un int. Sinon à un unsigned int. Tous les autres types sont modifiés par la promotion de nombre entier.

En termes clairs:

Tout type “plus petit” que int ou unsigned int est promu en int lorsqu’il est converti en un autre type de rang supérieur. C’est le travail du compilateur de s’assurer qu’un code C compilé pour une machine donnée (architecture) est conforme à la norme ISO-C à cet égard. char est défini par l’implémentation (en cours de signature ou non signée). Tous les autres types (promotion ou “rétrogradation”) sont définis par la mise en œuvre.

Qu’est-ce qui est défini par implémentation? Cela signifie qu’un compilateur donné se comportera systématiquement de la même manière sur une machine donnée. En d’autres termes, tout comportement “défini par l’implémentation” dépend à la fois du compilateur ET de la machine cible.

Pour créer un code portable:

  • encouragez toujours les valeurs de type C standard plus élevé.
  • Jamais “rétrograder” les valeurs à des types inférieurs.
  • Évitez toute implémentation “définie par l’implémentation” dans votre code.

Pourquoi cette folie définie par l’implémentation existe-t-elle si elle ruine l’effort des programmeurs ??? La programmation système nécessite essentiellement ce comportement défini par l’implémentation.

Donc plus spécifiquement vers votre question:

  • la troncature ne sera probablement pas protable. Sinon, la maintenance, le suivi des bogues, etc. nécessiteront beaucoup plus d’efforts que de simplement maintenir le code en utilisant des types de rangs plus élevés.
  • Si votre implémentation exécute des valeurs supérieures aux types impliqués, votre conception est erronée (sauf si vous êtes impliqué dans la programmation système).
  • En règle générale, le fait de passer de non signé à signé signifie préserve les valeurs mais pas l’inverse. Ainsi, lorsqu’une valeur non signée est opposée à une valeur signée, encouragez-le à signer au lieu de l’inverse.
  • Si l’utilisation de types de nombre entier aussi petits que possible est critique pour la mémoire dans votre application, vous devriez probablement revoir toute l’architecture du programme.