Signé à la conversion non signée en C – est-il toujours sûr?

Supposons que j’ai le code C suivant.

unsigned int u = 1234; int i = -5678; unsigned int result = u + i; 

Quelles sont les conversions implicites en cours, et ce code est-il sûr pour toutes les valeurs de u et i ? (Sûr, dans le sens où même si le résultat dans cet exemple va déborder à un nombre positif énorme, je pourrais le renvoyer dans un int et obtenir le résultat réel.)

Réponse courte

Votre i sera converti en un entier non signé en ajoutant UINT_MAX + 1 , puis l’ajout sera effectué avec les valeurs non signées, ce qui donnera un result important (dépendant des valeurs de u et i ).

Longue réponse

Selon la norme C99:

6.3.1.8 Conversions arithmétiques habituelles

  1. Si les deux opérandes ont le même type, aucune autre conversion n’est nécessaire.
  2. Sinon, si les deux opérandes ont des types entiers signés ou si les deux ont des types entiers non signés, l’opérande avec le type de rang de conversion entier inférieur est converti dans le type de l’opérande avec un rang supérieur.
  3. Sinon, si l’opérande qui a un type d’entier non signé a un rang supérieur ou égal au rang du type de l’autre opérande, l’opérande avec un type d’entier signé est converti dans le type de l’opérande avec un type d’entier non signé.
  4. Sinon, si le type de l’opérande avec un type d’entier signé peut représenter toutes les valeurs du type de l’opérande avec un type d’entier non signé, l’opérande avec un type d’entier non signé est convertie dans le type de l’opérande avec un type d’entier signé.
  5. Sinon, les deux opérandes sont convertis dans le type entier non signé correspondant au type de l’opérande avec un type entier signé.

Dans votre cas, nous en avons un unsigned int ( u ) et signé int ( i ). En se référant à (3) ci-dessus, puisque les deux opérandes ont le même rang, votre i devra être converti en un entier non signé.

6.3.1.3 Entiers signés et non signés

  1. Lorsqu’une valeur de type entier est convertie en un autre type entier autre que _Bool, si la valeur peut être représentée par le nouveau type, elle rest inchangée.
  2. Sinon, si le nouveau type n’est pas signé, la valeur est convertie en ajoutant ou en soustrayant de manière répétée un de plus que la valeur maximale pouvant être représentée dans le nouveau type jusqu’à ce que la valeur soit dans la plage du nouveau type.
  3. Sinon, le nouveau type est signé et la valeur ne peut pas y être représentée. soit le résultat est défini par l’implémentation ou un signal défini par l’implémentation est généré.

Maintenant, nous devons nous référer à (2) ci-dessus. Votre i sera converti en une valeur non signée en ajoutant UINT_MAX + 1 . Le résultat dépendra donc de la façon dont UINT_MAX est défini sur votre implémentation. Ce sera grand, mais ça ne débordera pas, car:

6.2.5 (9)

Un calcul impliquant des opérandes non signés ne peut jamais déborder, car un résultat qui ne peut pas être représenté par le type d’entier non signé résultant est réduit modulo par le nombre supérieur à la plus grande valeur pouvant être représentée par le type résultant.

Bonus: Conversion arithmétique Semi-WTF

 #include  int main(void) { unsigned int plus_one = 1; int minus_one = -1; if(plus_one < minus_one) printf("1 < -1"); else printf("boring"); return 0; } 

Vous pouvez utiliser ce lien pour l'essayer en ligne: http://codepad.org/yPhYCMFO

Bonus: Effet secondaire de conversion arithmétique

Les règles de conversion arithmétique peuvent être utilisées pour obtenir la valeur de UINT_MAX en initialisant une valeur non signée à -1 , à savoir:

 unsigned int umax = -1; // umax set to UINT_MAX 

Cela est garanti portable, quelle que soit la représentation du numéro signé du système en raison des règles de conversion décrites ci-dessus. Voir cette question SO pour plus d'informations: Est-il sécuritaire d'utiliser -1 pour définir tous les bits à true?

La conversion de signée en non signé ne se limite pas nécessairement à la copie ou à la réinterprétation de la représentation de la valeur signée. Citant le standard C (C99 6.3.1.3):

Lorsqu’une valeur de type entier est convertie en un autre type entier autre que _Bool, si la valeur peut être représentée par le nouveau type, elle rest inchangée.

Sinon, si le nouveau type n’est pas signé, la valeur est convertie en ajoutant ou en soustrayant de manière répétée un de plus que la valeur maximale pouvant être représentée dans le nouveau type jusqu’à ce que la valeur soit dans la plage du nouveau type.

Sinon, le nouveau type est signé et la valeur ne peut pas y être représentée. soit le résultat est défini par l’implémentation ou un signal défini par l’implémentation est généré.

Pour la représentation du complément à deux, presque universelle de nos jours, les règles correspondent à la réinterprétation des bits. Mais pour les autres représentations (complément de signe et d’amplitude ou de complément), l’implémentation C doit encore organiser le même résultat, ce qui signifie que la conversion ne peut pas simplement copier les bits. Par exemple, (unsigned) -1 == UINT_MAX, quelle que soit la représentation.

En général, les conversions en C sont définies pour opérer sur des valeurs et non sur des représentations.

Pour répondre à la question initiale:

 unsigned int u = 1234; int i = -5678; unsigned int result = u + i; 

La valeur de i est convertie en unsigned int, produisant UINT_MAX + 1 - 5678 . Cette valeur est ensuite ajoutée à la valeur non signée 1234, produisant UINT_MAX + 1 - 4444 .

(Contrairement au débordement non signé, le débordement signé appelle un comportement indéfini. Wraparound est commun, mais n’est pas garanti par le standard C – et les optimisations du compilateur peuvent causer des ravages sur le code qui fait des hypothèses injustifiées.)

Lorsqu’une variable non signée et une variable signée sont ajoutées (ou toute opération binary), les deux sont implicitement converties en non signées, ce qui entraînerait un résultat énorme.

Donc, il est sûr que le résultat pourrait être énorme et faux, mais il ne tombera jamais en panne.

Lors de la conversion de signé en non signé, il existe deux possibilités. Les nombres à l’origine positifs restnt (ou sont interprétés comme) la même valeur. Les nombres à l’origine négatifs seront désormais interprétés comme des nombres positifs plus importants.

Se référant à la bible :

  • Votre opération d’ajout entraîne la conversion de l’int en int non signé.
  • En supposant une représentation du complément à deux et des types de taille égale, le modèle de bit ne change pas.
  • La conversion d’un entier non signé en un entier signé dépend de l’implémentation. (Mais cela fonctionne probablement comme vous le souhaitez sur la plupart des plates-formes de nos jours.)
  • Les règles sont un peu plus compliquées dans le cas de la combinaison signée et non signée de tailles différentes.

Comme il a été répondu précédemment, vous pouvez effectuer des allers-retours entre signé et non signé sans problème. La casse des entiers signés est -1 (0xFFFFFFFF). Essayez d’append et de soustraire à cela et vous constaterez que vous pouvez rejeter et que ce soit correct.

Cependant, si vous envisagez de faire des allers-retours, je vous conseille vivement de nommer vos variables de manière à ce qu’elles soient claires, par exemple:

 int iValue, iResult; unsigned int uValue, uResult; 

Il est beaucoup trop facile de se laisser distraire par des problèmes plus importants et d’oublier quelle variable est de quel type si elle est nommée sans indice. Vous ne voulez pas convertir à un unsigned et ensuite l’utiliser comme un index de tableau.

Quelles conversions implicites se passent ici,

Je serai converti en un entier non signé.

et ce code est-il sûr pour toutes les valeurs de u et i?

Sûr dans le sens d’être bien défini oui (voir https://stackoverflow.com/a/50632/5083516 ).

Les règles sont généralement écrites en langage standard difficile à lire mais, quelle que soit la représentation utilisée dans l’entier signé, l’entier non signé contiendra une représentation du nombre en complément à deux.

L’addition, la soustraction et la multiplication fonctionneront correctement sur ces nombres, résultant en un autre entier non signé contenant un nombre à deux complément représentant le “résultat réel”.

la division et la conversion en types d’entiers non signés plus grands auront des résultats bien définis, mais ces résultats ne seront pas des représentations en complément à 2 du “résultat réel”.

(Sûr, dans le sens où même si le résultat dans cet exemple va déborder à un nombre positif énorme, je pourrais le renvoyer dans un int et obtenir le résultat réel.)

Alors que les conversions signées en non signées sont définies par le standard, l’inverse est défini par l’implémentation, gcc et msvc définissent la conversion de manière à obtenir le “résultat réel” lors de la conversion d’un nombre complémentaire 2 stocké dans un entier non signé. . Je pense que vous ne trouverez aucun autre comportement sur des systèmes obscurs qui n’utilisent pas le complément 2 pour les entiers signés.

https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html#Integers-implementation https://msdn.microsoft.com/en-us/library/0eex498h.aspx

Horrible Answers Galore

Ozgur Ozcitak

Lorsque vous passez de signé à non signé (et vice versa), la représentation interne du numéro ne change pas. Ce qui change, c’est comment le compilateur interprète le bit de signe.

C’est complètement faux.

Mats Fredriksson

Lorsqu’une variable non signée et une variable signée sont ajoutées (ou toute opération binary), les deux sont implicitement converties en non signées, ce qui entraînerait un résultat énorme.

C’est aussi faux. Les ints non signés peuvent être promus en ints s’ils ont la même précision en raison des bits de remplissage du type non signé.

smh

Votre opération d’ajout entraîne la conversion de l’int en int non signé.

Faux. Peut-être que oui et peut-être que non.

La conversion de unsigned int en signé est dépendant de l’implémentation. (Mais cela fonctionne probablement comme vous le souhaitez sur la plupart des plates-formes de nos jours.)

Faux. C’est soit un comportement indéfini si il provoque un débordement, soit la valeur est préservée.

Anonyme

La valeur de i est convertie en unsigned int …

Faux. Dépend de la précision d’un int par rapport à un unsigned int.

Taylor Price

Comme il a été répondu précédemment, vous pouvez effectuer des allers-retours entre signé et non signé sans problème.

Faux. Tenter de stocker une valeur en dehors de la plage d’un entier signé entraîne un comportement indéfini.

Maintenant, je peux enfin répondre à la question.

Si la précision de int est égale à unsigned int, u sera promu en un int signé et vous obtiendrez la valeur -4444 de l’expression (u + i). Maintenant, si u et i ont d’autres valeurs, vous pouvez avoir un comportement de dépassement de capacité et d’indéfini, mais avec ces nombres exacts, vous obtiendrez -4444 [1] . Cette valeur aura le type int. Mais vous essayez de stocker cette valeur dans un int non signé afin de le convertir en un entier non signé et la valeur obtenue sera (UINT_MAX + 1) – 4444.

Si la précision de unsigned int est supérieure à celle d’un int, l’int dédicacé sera promu en un unsigned int donnant la valeur (UINT_MAX + 1) – 5678 qui sera ajoutée à l’autre unsigned int 1234. d’autres valeurs, qui rendent l’expression en dehors de la plage {0..UINT_MAX}, la valeur (UINT_MAX + 1) sera ajoutée ou soustraite jusqu’à ce que le résultat ne tombe pas dans la plage {0..UINT_MAX) et aucun comportement indéfini ne se produira .

Qu’est ce que la précision?

Les entiers ont des bits de remplissage, des bits de signe et des bits de valeur. Les entiers non signés n’ont évidemment pas de bit de signe. Le caractère non signé est en outre garanti de ne pas avoir de bits de remplissage. Le nombre de valeurs dont dispose un entier est la précision dont il dispose.

[Gotchas]

La macro sizeof macro seule ne peut pas être utilisée pour déterminer la précision d’un entier si des bits de remplissage sont présents. Et la taille d’un octet ne doit pas nécessairement être un octet (huit bits) défini par C99.

[1] Le débordement peut se produire à l’un des deux points. Soit avant l’ajout (pendant la promotion) – lorsque vous avez un int non signé qui est trop grand pour tenir dans un int. Le débordement peut également se produire après l’ajout, même si int non signé était dans l’intervalle d’un int, après l’addition, le résultat peut toujours déborder.


Sur une note sans rapport, je suis un étudiant récemment diplômé qui tente de trouver du travail;)