Question

Supposons que j'ai le code C suivant.

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

unsigned int result = u + i;

Quelles conversions implicites ont lieu ici et ce code est-il sûr pour toutes les valeurs de u et i?(Sûr, dans le sens où même si résultat dans cet exemple, cela débordera vers un énorme nombre positif, je pourrais le renvoyer à un int et obtenez le vrai résultat.)

Était-ce utile?

La solution

Réponse courte

Ton i sera converti à un entier non signé en ajoutant UINT_MAX + 1, alors l'addition sera effectuée avec les valeurs non signées, ce qui donnera un grand result (en fonction 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 en type de l'opérande avec le rang le plus élevé.
  3. Sinon, si l'opérande de type entier non signé a un rang supérieur ou égal au rang du type de l'autre opérande, alors l'opérande de type entier signé est converti en type de l'opérande de type entier non signé.
  4. Sinon, si le type de l'opérande de type entier signé peut représenter toutes les valeurs du type de l'opérande de type entier non signé, alors l'opérande de type entier non signé est converti en type de l'opérande de type entier signé.
  5. Sinon, les deux opérandes sont convertis en type entier non signé correspondant au type de l'opérande de type entier signé.

Dans votre cas, nous avons un int non signé (u) et signé int (i).En référence à (3) ci-dessus, puisque les deux opérandes ont le même rang, votre i devra être converti à 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 reste inchangée.
  2. Sinon, si le nouveau type n'est pas signé, la valeur est convertie en ajoutant ou en soustrayant à plusieurs reprises une valeur 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, soit un signal défini par l'implémentation est émis.

Nous devons maintenant nous référer à (2) ci-dessus.Ton 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.Il sera grand, mais il 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 entier non signé résultant est réduit modulo le nombre supérieur à la plus grande valeur pouvant être représentée par le type résultant.

Prime:Conversion arithmétique Semi-WTF

#include <stdio.h>

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 essayer ceci en ligne : https://repl.it/repls/QuickWhimsicalBytes

Prime:Effet secondaire de la conversion arithmétique

Des 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, c'est à dire:

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

La portabilité est garantie quelle que soit la représentation numérique signée 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 sur vrai ?

Autres conseils

La conversion de signé en non signé fait pas il suffit nécessairement de copier ou de réinterpréter la représentation de la valeur signée.Citant la norme C (C99 6.3.1.3) :

Lorsqu'une valeur avec un 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 est inchangée.

Sinon, si le nouveau type n'est pas signé, la valeur est convertie en ajoutant ou en soustrayant à plusieurs reprises une de plus que la valeur maximale qui peut ê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, soit un signal défini par l'implémentation est augmenté.

Pour la représentation en complément à deux, quasi universelle de nos jours, les règles correspondent bien à une réinterprétation des bits.Mais pour les autres représentations (signe et grandeur ou complément à un), l'implémentation C doit toujours obtenir 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 entier non signé, ce qui donne UINT_MAX + 1 - 5678.Cette valeur est ensuite ajoutée à la valeur non signée 1234, ce qui donne UINT_MAX + 1 - 4444.

(Contrairement au débordement non signé, le débordement signé appelle un comportement non défini.Le bouclage est courant, mais n'est pas garanti par le standard C - et les optimisations du compilateur peuvent faire des ravages sur le code qui fait des hypothèses injustifiées.)

Se référant à la Bible:

  • Votre opération d'addition entraîne la conversion de l'int en un int non signé.
  • En supposant une représentation en complément à deux et des types de taille égale, la configuration binaire 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 l’attendez sur la plupart des plateformes de nos jours.)
  • Les règles sont un peu plus compliquées dans le cas de la combinaison de signés et non signés de tailles différentes.

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

C'est donc sûr dans le sens où le résultat peut être énorme et erroné, mais il ne plantera jamais.

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

Comme cela a été répondu précédemment, vous pouvez effectuer une conversion entre signé et non signé sans problème.Le cas limite pour les entiers signés est -1 (0xFFFFFFFF).Essayez d'en ajouter et de soustraire et vous constaterez que vous pouvez revenir en arrière et que ce soit correct.

Cependant, si vous envisagez d'effectuer du casting dans les deux sens, je vous conseille fortement de nommer vos variables de manière à ce que leur type soit clair, 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 elles sont nommées sans indice.Vous ne voulez pas effectuer de conversion en un signe non signé, puis l'utiliser comme index de tableau.

Quelles conversions implicites se produisent 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 écrites dans un langage standard généralement difficile à lire, mais essentiellement, quelle que soit la représentation utilisée dans l'entier signé, l'entier non signé contiendra une représentation en complément à 2 du nombre.

L'addition, la soustraction et la multiplication fonctionneront correctement sur ces nombres, ce qui donnera lieu à un autre entier non signé contenant un nombre complémentaire à deux représentant le « résultat réel ».

la division et la conversion en types 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 de cet exemple débordera vers un énorme nombre positif, je pourrais le renvoyer à un int et obtenir le résultat réel.)

Alors que les conversions de signé en non signé sont définies par la norme, l'inverse est défini par l'implémentation, gcc et msvc définissent la conversion de telle sorte que vous obtiendrez le "résultat réel" lors de la conversion d'un nombre complémentaire à 2 stocké dans un entier non signé en un entier signé. .J'espère que vous ne trouverez d'autres comportements que 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

Des réponses horribles à profusion

Ozgur Ozcitak

Lorsque vous jetez de signé à unsigned (et vice versa), la représentation interne du nombre 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 binaire) toutes deux sont implicitement converties en non signées, ce qui dans ce cas entraînerait un résultat énorme.

C'est également faux.Les entiers non signés peuvent être promus en entiers s'ils ont une précision égale en raison des bits de remplissage dans le type non signé.

smh

Votre opération d'addition provoque la converti INT en un INT non signé.

Faux.Peut-être que c'est le cas et peut-être que ce n'est pas le cas.

La conversion de l'intre non signé à Int signé est dépendante de l'implémentation.(Mais cela fonctionne probablement comme vous vous attendez sur la plupart des plateformes de nos jours.)

Faux.Il s'agit soit d'un comportement indéfini s'il provoque un débordement, soit si la valeur est conservée.

Anonyme

La valeur de i est convertie en int ... non signé ...

Faux.Dépend de la précision d'un int par rapport à un int non signé.

Prix ​​​​Taylor

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

Faux.Essayer 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 à un int non signé, u sera promu en entier signé et vous obtiendrez la valeur -4444 à partir de l'expression (u+i).Maintenant, si vous et moi avons d'autres valeurs, vous risquez d'avoir un débordement et un comportement indéfini, mais avec ces chiffres exacts, vous obtiendrez -4444. [1].Cette valeur sera de type int.Mais vous essayez de stocker cette valeur dans un entier non signé afin qu'elle soit ensuite convertie en un entier non signé et la valeur que le résultat finira par avoir serait (UINT_MAX+1) - 4444.

Si la précision d'un entier non signé est supérieure à celle d'un entier, l'entier signé sera promu en entier non signé donnant la valeur (UINT_MAX+1) - 5678 qui sera ajoutée à l'autre entier non signé 1234.Si u et i ont d'autres valeurs, qui font que l'expression tombe en dehors de la plage {0..UINT_MAX}, la valeur (UINT_MAX+1) sera soit ajoutée, soit soustraite jusqu'à ce que le résultat tombe 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.Il est en outre garanti que les caractères non signés n'auront pas de bits de remplissage.Le nombre de bits de valeurs d’un entier correspond à sa précision.

[Des pièges]

La taille de la 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) tel que défini par C99.

[1] Le débordement peut se produire en 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 l'int non signé était dans la plage d'un int, après l'ajout, le résultat peut toujours déborder.


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

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top