Pourquoi 0 <-0x80000000?

J’ai ci-dessous un programme simple:

#include  #define INT32_MIN (-0x80000000) int main(void) { long long bal = 0; if(bal < INT32_MIN ) { printf("Failed!!!"); } else { printf("Success!!!"); } return 0; } 

La condition if(bal < INT32_MIN ) est toujours vraie. Comment est-ce possible?

Cela fonctionne bien si je change la macro pour:

 #define INT32_MIN (-2147483648L) 

Quelqu’un peut-il signaler le problème?

C’est assez subtil.

Chaque littéral entier dans votre programme a un type. Le type dont il dispose est régi par un tableau du 6.4.4.1:

 Suffix Decimal Constant Octal or Hexadecimal Constant none int int long int unsigned int long long int long int unsigned long int long long int unsigned long long int 

Si un nombre littéral ne rentre pas dans le type int par défaut, il essaiera le type suivant, comme indiqué dans le tableau ci-dessus. Donc, pour les littéraux entiers décimaux, il va comme:

  • Essayer int
  • Si ça ne va pas, essayez long
  • Si ça ne va pas, essayez long long .

Les littéraux hexadécimaux se comportent cependant différemment! Si le littéral ne rentre pas dans un type signé comme int , il essaiera d’abord unsigned int avant de passer à des types plus grands. Voir la différence dans le tableau ci-dessus.

Donc, sur un système 32 bits, votre littéral 0x80000000 est de type unsigned int .

Cela signifie que vous pouvez appliquer l’opérateur unaire sur le littéral sans invoquer le comportement défini par l’implémentation, comme vous le feriez par ailleurs lors du débordement d’un entier signé. Au lieu de cela, vous obtiendrez la valeur 0x80000000 , une valeur positive.

bal < INT32_MIN appelle les conversions arithmétiques habituelles et le résultat de l'expression 0x80000000 est promu de unsigned int à long long . La valeur 0x80000000 est préservée et 0 est inférieure à 0x80000000, d'où le résultat.

Lorsque vous remplacez le littéral par 2147483648L vous utilisez la notation décimale et, par conséquent, le compilateur ne sélectionne unsigned int , mais essaie plutôt de l'intégrer dans un long . Le suffixe L indique également que vous voulez un long si possible . Le suffixe L a des règles similaires si vous continuez à lire la table mentionnée au 6.4.4.1: si le nombre ne correspond pas à la long demandée, ce qui n’est pas le cas dans le cas 32 bits, le compilateur vous laissera beaucoup de long long où ça ira très bien.

0x80000000 est un littéral unsigned avec la valeur 2147483648.

Appliquer le moins unaire sur ceci vous donne toujours un type non signé avec une valeur non nulle. (En fait, pour une valeur non nulle x , la valeur que vous UINT_MAX - x + 1 est UINT_MAX - x + 1 )

Ce littéral entier 0x80000000 a le type unsigned int .

Selon la norme C (6.4.4.1 Constantes entières)

5 Le type d’une constante entière est le premier de la liste correspondante dans laquelle sa valeur peut être représentée.

Et cette constante entière peut être représentée par le type de unsigned int .

Donc cette expression

-0x80000000 a le même type unsigned int . De plus, il a la même valeur 0x80000000 dans la représentation du complément à deux qui calcule la façon suivante

 -0x80000000 = ~0x80000000 + 1 => 0x7FFFFFFF + 1 => 0x80000000 

Cela a un effet secondaire si vous écrivez par exemple

 int x = INT_MIN; x = abs( x ); 

Le résultat sera à nouveau INT_MIN .

Ainsi dans cet état

 bal < INT32_MIN 

on compare 0 avec la valeur non signée 0x80000000 convertie en type long long int selon les règles des conversions arithmétiques habituelles.

Il est évident que 0 est inférieur à 0x80000000 .

La constante numérique 0x80000000 est de type unsigned int . Si nous prenons -0x80000000 et faisons 2s complimenter les maths, nous obtenons ceci:

 ~0x80000000 = 0x7FFFFFFF 0x7FFFFFFF + 1 = 0x80000000 

Donc -0x80000000 == 0x80000000 . Et comparer (0 < 0x80000000) (puisque 0x80000000 est non signé) est vrai.

Un sharepoint confusion se produit en pensant que - fait partie de la constante numérique.

Dans le code ci-dessous 0x80000000 est la constante numérique. Son type est déterminé uniquement sur cela. Le - est appliqué par la suite et ne change pas le type .

 #define INT32_MIN (-0x80000000) long long bal = 0; if (bal < INT32_MIN ) 

Les constantes numériques brutes sans fioritures sont positives.

Si c'est décimal, alors le type assigné est le premier type qui le tiendra: int , long , long long .

Si la constante est octale ou hexadécimale, elle obtient le premier type qui la contient: int , unsigned , long , unsigned long , long long , unsigned long long .

0x80000000 , sur le système OP obtient le type de unsigned ou unsigned long . De toute façon, c'est un type non signé.

-0x80000000 est également une valeur non nulle et étant un type non signé, il est supérieur à 0. Lorsque le code compare cela à une long long , les valeurs ne sont pas modifiées sur les 2 côtés de la comparaison, donc 0 < INT32_MIN est vrai.


Une autre définition évite ce comportement curieux

 #define INT32_MIN (-2147483647 - 1) 

Marchons un moment dans un monde fantastique où int et unsigned ont 48 bits.

Alors 0x80000000 tient dans int et est donc le type int . -0x80000000 est alors un nombre négatif et le résultat de l'impression est différent.

[Retour au vrai mot]

Étant donné que 0x80000000 tient dans un type non signé avant un type signé car il est juste plus grand que some_signed_MAX dans some_unsigned_MAX , il s'agit d'un type non signé.

C a pour règle que le littéral de type entier peut être signed ou unsigned , selon qu’il convient ou unsigned à une signed unsigned (promotion en nombre entier). Sur une machine 32 bits, le littéral 0x80000000 sera unsigned . Le complément 2 de -0x80000000 est 0x80000000 sur une machine 32 bits. Par conséquent, la bal < INT32_MIN comparaison bal < INT32_MIN est entre signed et unsigned et avant que la comparaison selon la règle C unsigned int ne soit convertie en long long .

C11: 6.3.1.8/1:

[...] Sinon, si le type de l'opérande avec un type entier signé peut représenter toutes les valeurs du type de l'opérande avec un type entier non signé, l'opérande avec un type entier non signé est converti dans le type de l'opérande avec type entier signé.

Par conséquent, bal < INT32_MIN est toujours true .