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:
int
long
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
.
[...] 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
.