Conversion non signée en C - est-ce toujours sûr?

135

Supposons que j'ai le code C suivant.

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

unsigned int result = u + i;

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

Cwick
la source

Réponses:

223

Réponse courte

Votre isera converti en un entier non signé par addition UINT_MAX + 1, puis l'ajout sera effectué avec les valeurs non signées, ce qui donnera un grand result(en fonction des valeurs de uet i).

Longue réponse

Selon la norme C99:

6.3.1.8 Conversions arithmétiques usuelles

  1. Si les deux opérandes ont le même type, aucune conversion supplémentaire n'est nécessaire.
  2. Sinon, si les deux opérandes ont des types entiers signés ou les deux ont des types entiers non signés, l'opérande avec le type de rang de conversion d'entier inférieur est converti en type de l'opérande de rang supérieur.
  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, 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 au type entier non signé correspondant au type de l'opérande avec le type entier signé.

Dans votre cas, nous avons un int ( u) non signé et un int ( ) signé i. En se référant à (3) ci-dessus, puisque les deux opérandes ont le même rang, vous idevrez ê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 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 se trouve 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. Votre isera converti en une valeur non signée en ajoutant UINT_MAX + 1. Le résultat dépendra donc de la manière dont UINT_MAXest défini votre implémentation. Il sera volumineux, 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 qui est supérieur de un à la plus grande valeur pouvant être représentée par le type résultant.

Bonus: 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

Bonus: effet secondaire de conversion arithmétique

Les règles de conversion arithmétique peuvent être utilisées pour obtenir la valeur de UINT_MAXen initialisant une valeur non signée à -1, c'est- à -dire:

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

Ceci est garanti pour être portable 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ûr d'utiliser -1 pour définir tous les bits sur true?

Ozgur Ozcitak
la source
Je ne comprends pas pourquoi il ne peut pas simplement faire une valeur absolue et ensuite traiter comme non signé, tout comme avec des nombres positifs?
Jose Salvatierra
7
@ D.Singh pouvez-vous indiquer les mauvaises parties de la réponse?
Shmil The Cat
Pour la conversion signée en non signée, nous ajoutons la valeur maximale de la valeur non signée (UINT_MAX +1). De même, quel est le moyen le plus simple de passer de non signé à signé? Doit-on soustraire le nombre donné de la valeur max (256 en cas de caractère non signé)? Par exemple: 140 lorsqu'il est converti en nombre signé devient -116. Mais 20 devient 20 lui-même. Alors, un truc facile ici?
Jon Wheelock
@JonWheelock voir: stackoverflow.com/questions/8317295/…
Ozgur Ozcitak
24

La conversion de signé en non signé ne se contente pas nécessairement de copier ou de réinterpréter 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 reste inchangée.

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 se trouve 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 émis.

Pour la représentation du complément à deux qui est presque universelle de nos jours, les règles correspondent à la réinterprétation des bits. Mais pour les autres représentations (signe-et-magnitude ou complément à uns), l'implémentation C doit toujours organiser le même résultat, ce qui signifie que la conversion ne peut pas simplement copier les bits. Par exemple, (non signé) -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, pas 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 un 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 aux débordements non signés, les débordements signés invoquent 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 suppositions injustifiées.)


la source
5

En référence à la Bible :

  • Votre opération d'addition entraîne la conversion de l'int en un int non signé.
  • En supposant une représentation complémentaire à deux et des types de taille égale, le motif de bits 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 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.
smh
la source
3

Quand 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 dans ce cas entraînerait un résultat énorme.

Il est donc sûr que le résultat peut être énorme et faux, mais il ne plantera jamais.

Mats Fredriksson
la source
Pas vrai. 6.3.1.8 Conversions arithmétiques usuelles Si vous additionnez un int et un caractère non signé, ce dernier est converti en int. Si vous additionnez deux caractères non signés, ils sont convertis en int.
2501
3

Lors de la conversion de signé à non signé, il existe deux possibilités. Les nombres initialement positifs conservent (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.

Anneau de Tim
la source
1

Comme il a été répondu précédemment, vous pouvez passer sans problème entre signé et non signé. La casse de la bordure pour les entiers signés est -1 (0xFFFFFFFF). Essayez d'ajouter et de soustraire de cela et vous constaterez que vous pouvez renvoyer et que ce soit correct.

Cependant, si vous prévoyez de lancer un casting d'avant en arrière, je vous conseillerais 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 s'ils sont nommés sans indice. Vous ne voulez pas convertir en un non signé, puis l'utiliser comme index de tableau.

Prix ​​Taylor
la source
0

Quelles conversions implicites sont en cours ici,

i sera 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 difficile à lire, mais essentiellement quelle que soit la représentation utilisée dans l'entier signé, l'entier non signé contiendra une représentation complémentaire du nombre à 2.

L'addition, la soustraction et la multiplication fonctionneront correctement sur ces nombres résultant en un autre entier non signé contenant un nombre de complément à 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 complémentaires de 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 nombre positif énorme, je pourrais le renvoyer à un int et obtenir le résultat réel.)

Alors que les conversions de signé à 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 de complément à 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 de 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

plugwash
la source
-17

Réponses horribles à gogo

Ozgur Ozcitak

Lorsque vous transtypez de signé à non signé (et vice versa), la représentation interne du nombre ne change pas. Ce qui change, c'est la façon dont le compilateur interprète le bit de signe.

C'est complètement faux.

Mats Fredriksson

Quand 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 dans ce cas entraînerait un résultat énorme.

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

smh

Votre opération d'addition entraîne la conversion de l'int en un int non signé.

Faux. Peut-être que oui et peut-être 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 plates-formes de nos jours.)

Faux. Il s'agit d'un comportement non défini s'il provoque un débordement ou la valeur est conservée.

Anonyme

La valeur de i est convertie en un entier 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 passer sans problème entre signé et non signé.

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 à unsigned int, u sera promu en int signé et vous obtiendrez la valeur -4444 à partir de l'expression (u + i). Maintenant, si u et moi avons d'autres valeurs, vous pouvez avoir un comportement de débordement et 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 entier non signé afin qu'il soit ensuite converti en un entier non signé et la valeur que le résultat finira par avoir serait (UINT_MAX + 1) - 4444.

Si la précision de unsigned int est supérieure à celle d'un int, le signé int sera promu en unsigned int donnant la valeur (UINT_MAX + 1) - 5678 qui sera ajoutée à l'autre unsigned int 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. Le caractère non signé est en outre garanti pour ne pas avoir de bits de remplissage. Le nombre de bits de valeurs d'un entier correspond à sa précision.

[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) comme 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 l'int non signé était dans la plage d'un int, après l'ajout, le résultat peut encore déborder.

Elite Mx
la source
6
"Les entiers non signés peuvent être promus en entiers". Pas vrai. Aucune promotion d' entier ne se produit car les types sont déjà rang> = int. 6.3.1.1: "Le rang de tout type entier non signé doit être égal au rang du type entier signé correspondant, le cas échéant." et 6.3.1.8: "Sinon, si l'opérande qui a le type entier non signé a un rang supérieur ou égal au rang du type de l'autre opérande, alors l'opérande avec le type entier signé est converti en type de l'opérande avec entier non signé type." à la fois garantie intest converti unsigned intlorsque les conversions arithmétiques habituelles s'appliquent.
CB Bailey
1
6.3.1.8 Se produit uniquement après la promotion d'entiers. Le paragraphe d'ouverture dit "Sinon, les promotions entières sont effectuées sur les deux opérandes. ALORS les règles suivantes sont appliquées aux opérandes promus". Alors allez lire les règles de promotion 6.3.1.1 ... "Un objet ou une expression avec un type entier dont le rang de conversion entier est inférieur ou EQUAL au rang de int et unsigned int" et "Si un int peut représenter toutes les valeurs du type d'origine, la valeur est convertie en un entier ".
Elite Mx
1
6.3.1.1 La promotion d'entiers utilisée pour convertir certains types d'entiers qui ne le sont pas intou unsigned inten l'un de ces types où quelque chose de type unsigned intou intest attendu. Le "ou égal" a été ajouté dans TC2 pour permettre aux types énumérés de rang de conversion égal intou unsigned intà être convertis en l'un de ces types. Il n'a jamais été prévu que la promotion décrite se transforme entre unsigned intet int. La détermination de type commun entre unsigned intet intest toujours régie par 6.3.1.8, même après TC2.
CB Bailey
19
Publier de fausses réponses tout en critiquant les mauvaises réponses des autres ne semble pas être une bonne stratégie pour trouver du travail ... ;-)
R .. GitHub STOP AIDING ICE
6
Je ne vote pas pour la suppression car ce niveau de méfait combiné à l'arrogance est trop amusant
MM