Pourquoi 0 <-0x80000000?

253

J'ai ci-dessous un programme simple:

#include <stdio.h>

#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 en:

#define INT32_MIN        (-2147483648L)

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

Jayesh Bhoi
la source
3
Combien ça coûte CHAR_BIT * sizeof(int)?
5gon12eder
1
Avez-vous essayé d'imprimer bal?
Ryan Fitzpatrick
10
À mon humble avis la chose la plus intéressante est qu'il est vrai que pour -0x80000000, mais faux pour -0x80000000L, -2147483648et -2147483648L(gcc 4.1.2), la question est donc: pourquoi est l'int littéral -0x80000000différent du littéral int -2147483648?
Andreas Fester
2
@Bathsheba Je viens de lancer le programme sur le compilateur en ligne tutorialspoint.com/codingground.htm
Jayesh Bhoi
2
Si vous avez déjà remarqué que (certaines incarnations de) se <limits.h>définissent INT_MINcomme (-2147483647 - 1), vous savez maintenant pourquoi.
zwol

Réponses:

363

C'est assez subtil.

Chaque littéral entier de votre programme a un type. Son type est réglementé 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 peut pas entrer dans le inttype par défaut , il tentera le type plus grand suivant comme indiqué dans le tableau ci-dessus. Donc, pour les littéraux entiers décimaux réguliers, cela se passe comme suit:

  • Essayer int
  • Si cela ne peut pas aller, essayez long
  • Si cela ne peut pas rentrer, essayez long long.

Les littéraux hexagonaux se comportent cependant différemment! Si le littéral ne peut pas entrer dans un type signé int, il essaiera d'abord unsigned intavant 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 0x80000000est 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 autrement en cas de débordement d'un entier signé. Au lieu de cela, vous obtiendrez la valeur 0x80000000, une valeur positive.

bal < INT32_MINinvoque les conversions arithmétiques habituelles et le résultat de l'expression 0x80000000est promu de unsigned intà long long. La valeur 0x80000000est conservée et 0 est inférieur à 0x80000000, d'où le résultat.

Lorsque vous remplacez le littéral par 2147483648Lvous utilisez la notation décimale et donc le compilateur ne choisit pas unsigned int, mais essaie plutôt de l'adapter à l'intérieur d'un long. Le suffixe L indique également que vous en voulez long si possible . Le suffixe L a en fait des règles similaires si vous continuez à lire le tableau mentionné au 6.4.4.1: si le nombre ne rentre pas dans le demandé long, ce qui n'est pas le cas dans le cas 32 bits, le compilateur vous donnera un long longoù il conviendra très bien.

Lundin
la source
3
"... remplacez le littéral par -2147483648L vous obtenez explicitement un long, qui est signé." Hmmm, Dans un 32 bits longsystème 2147483648L, ne rentre pas dans un long, il devient long long, alors l' -est appliqué - ce que je pensais.
chux
2
@ASH Parce que le nombre maximum qu'un int peut avoir est alors 0x7FFFFFFF. Essayez-le vous-même:#include <limits.h> printf("%X\n", INT_MAX);
Lundin
5
@ASH Ne confondez pas la représentation hexadécimale des littéraux entiers dans le code source avec la représentation binaire sous-jacente d'un nombre signé. Le littéral 0x7FFFFFFFlorsqu'il est écrit en code source est toujours un nombre positif, mais votre intvariable peut bien sûr contenir des nombres binaires bruts jusqu'à la valeur 0xFFFFFFFF.
Lundin
2
@ASH ìnt n = 0x80000000force une conversion du littéral non signé en un type signé. Ce qui se passera dépend de votre compilateur - c'est un comportement défini par l'implémentation. Dans ce cas, il a choisi d'afficher le littéral entier dans le int, en écrasant le bit de signe. Sur d'autres systèmes, il peut ne pas être possible de représenter le type et vous invoquez un comportement indéfini - le programme peut se bloquer. Vous obtiendrez le même comportement si vous le faites int n=2147483648;, il n'est pas du tout lié à la notation hexadécimale.
Lundin
3
L'explication de la façon dont unaire -est appliqué aux entiers non signés pourrait être développée un peu. J'avais toujours supposé (bien que je ne me sois heureusement jamais appuyé sur l'hypothèse) que les valeurs non signées seraient "promues" en valeurs signées, ou peut-être que le résultat ne serait pas défini. (Honnêtement, cela devrait être une erreur de compilation; qu'est-ce que - 3ucela signifie même?)
Kyle Strand
27

0x80000000est un unsignedlittéral avec une valeur 2147483648.

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

Bathsheba
la source
23

Ce littéral entier 0x80000000a un 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

-0x80000000a le même unsigned inttype. De plus, il a la même valeur 0x80000000dans la représentation du complément à deux qui calcule la manière suivante

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

Cela a un effet secondaire si écrire par exemple

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

Le résultat sera à nouveau INT_MIN.

Ainsi dans cet état

bal < INT32_MIN

il est comparé 0à une valeur non signée0x80000000 convertie en type long long int selon les règles des conversions arithmétiques habituelles.

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

Vlad de Moscou
la source
12

La constante numérique 0x80000000est de type unsigned int. Si nous prenons -0x80000000et faisons des maths complimentaires à ce sujet, nous obtenons ceci:

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

Alors -0x80000000 == 0x80000000. Et comparer (0 < 0x80000000)(car 0x80000000non signé) est vrai.

dbush
la source
Cela suppose des ints 32 bits . Bien que ce soit un choix très courant, dans une implémentation donnée, elle intpeut être plus étroite ou plus large. Il s'agit toutefois d'une analyse correcte pour ce cas.
John Bollinger
Ceci n'est pas pertinent pour le code OP, -0x80000000est une arithmétique non signée. ~0x800000000est un code différent.
MM
Cela semble être la meilleure et la bonne réponse pour moi, tout simplement. @MM, il explique comment prendre un complément à deux. Cette réponse traite spécifiquement de ce que le signe négatif fait au nombre.
Octopus
@Octopus le signe négatif n'applique pas le complément de 2 au nombre (!) Bien que cela semble clair, il ne décrit pas ce qui se passe dans le code -0x80000000! En fait, le complément de 2 n'a absolument rien à voir avec cette question.
MM
12

Un point de confusion survient lorsque l' -on pense que cela fait partie de la constante numérique.

Dans le code ci-dessous se 0x80000000trouve la constante numérique. Son type n'est déterminé que 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 non décorées sont positives.

Si elle est décimal, le type attribué est premier type qui va le tenir: int, long, long long.

Si la constante est octal ou hexadécimal, il obtient le premier type qui le tient: int, unsigned, long, unsigned long, long long, unsigned long long.

0x80000000, sur le système OP obtient le type de unsignedou unsigned long. Quoi qu'il en soit, il s'agit d'un type non signé.

-0x80000000est également une valeur non nulle et étant un type non signé, il est supérieur à 0. Lorsque le code compare cela à a long long, les valeurs ne sont pas modifiées sur les 2 côtés de la comparaison, il en 0 < INT32_MINva de même.


Une autre définition évite ce comportement curieux

#define INT32_MIN        (-2147483647 - 1)

Laissez-nous marcher dans un pays fantastique pendant un certain temps où intet unsignedsont 48 bits.

Puis 0x80000000rentre dans intet est donc le type int. -0x80000000est alors un nombre négatif et le résultat de l'impression est différent.

[Retour au mot réel]

Étant donné qu'il 0x80000000tient dans un type non signé avant un type signé car il est juste plus grand some_signed_MAXqu'encore some_unsigned_MAX, il s'agit d'un type non signé.

chux - Réintégrer Monica
la source
8

C a une règle selon laquelle le littéral entier peut être signedou unsigneddépend de s'il s'inscrit dans signedou unsigned(promotion entière). Sur une 32machine à bits, le littéral 0x80000000sera unsigned. Le complément de 2 se -0x80000000trouve 0x80000000 sur une machine 32 bits. Par conséquent, la comparaison bal < INT32_MINest entre signedet unsignedet avant la comparaison selon la règle C unsigned intsera convertie en long long.

C11: 6.3.1.8/1:

[...] 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 d'opérande avec type entier signé.

Par conséquent, bal < INT32_MINc'est toujours true.

haccks
la source