web-dev-qa-db-fra.com

Pourquoi int i = 1024 * 1024 * 1024 * 1024 se compile-t-il sans erreur?

La limite de int est comprise entre -2147483648 et 2147483647.

Si je saisis

int i = 2147483648;

alors Eclipse demandera un soulignement rouge sous "2147483648".

Mais si je fais ça:

int i = 1024 * 1024 * 1024 * 1024;

ça va compiler bien.

public class Test {
    public static void main(String[] args) {        

        int i = 2147483648;                   // error
        int j = 1024 * 1024 * 1024 * 1024;    // no error

    }
}

C'est peut-être une question fondamentale en Java, mais je ne vois pas pourquoi la deuxième variante ne produit aucune erreur.

150
WUJ

Il n'y a rien de mal à cette déclaration; vous ne faites que multiplier 4 nombres et l'assigner à un int, il se trouve qu'il y a un débordement. Cela diffère de l'attribution d'un seul littéral, qui serait vérifié par les bornes au moment de la compilation.

C'est le hors-jeu littéral qui cause l'erreur, pas le affectation:

System.out.println(2147483648);        // error
System.out.println(2147483647 + 1);    // no error

En revanche, un littéral long compilerait bien:

System.out.println(2147483648L);       // no error

Notez que, en fait, le résultat est est toujours calculé au moment de la compilation car 1024 * 1024 * 1024 * 1024 est un expression constante :

int i = 1024 * 1024 * 1024 * 1024;

devient:

   0: iconst_0      
   1: istore_1      

Notez que le résultat (0) est simplement chargé et stocké et aucune multiplication n’a lieu.


De JLS §3.10.1 (merci à @ChrisK pour l'avoir signalé dans les commentaires):

Il s'agit d'une erreur de compilation si un littéral décimal de type int est supérieur à 2147483648 (231), ou si le littéral décimal 2147483648 apparaît n'importe où autrement que comme l'opérande de l'opérateur unaire moins ( §15.15.4 ).

232
arshajii

1024 * 1024 * 1024 * 1024 et 2147483648 n'ont pas la même valeur en Java.

Réellement, 2147483648 N'EST PAS MÊME UNE VALEUR (bien que 2147483648L _ est) en Java. Le compilateur ne sait littéralement pas ce que c'est ni comment l'utiliser. Alors ça pleure.

1024 est un int valide en Java, et un int valide multiplié par un autre int valide, est toujours un int valide. Même si ce n'est pas la même valeur que ce à quoi vous vous attendriez intuitivement car le calcul débordera.

Exemple

Prenons l'exemple de code suivant:

public static void main(String[] args) {
    int a = 1024;
    int b = a * a * a * a;
}

Vous attendriez-vous à ce que cela génère une erreur de compilation? Cela devient un peu plus glissant maintenant.
Et si on mettait une boucle avec 3 itérations et qu'on la multipliait?

Le compilateur est autorisé à optimiser, mais il ne peut pas changer le comportement du programme en le faisant.


Quelques informations sur la manière dont cette affaire est réellement traitée:

Dans Java et de nombreux autres langages, les entiers consisteront en un nombre fixe de bits. Les calculs qui ne rentrent pas dans le nombre de bits donné vont débordement ; le calcul est fondamentalement exécuté module 2 ^ 32 en Java, après quoi la valeur est reconvertie en un signé entier.

D'autres langages ou API utilisent un nombre de bits dynamique (BigInteger en Java), déclenchent une exception ou définissent la valeur sur une valeur magique telle que not-a-number.

43
Cruncher

Je ne sais pas pourquoi la deuxième variante ne produit aucune erreur.

Le comportement que vous suggérez - c'est-à-dire la production d'un message de diagnostic lorsqu'un calcul produit une valeur supérieure à la plus grande valeur pouvant être stockée dans un entier - est un caractéristique. Pour que vous puissiez utiliser n'importe quelle fonctionnalité, celle-ci doit être pensée, considérée comme une bonne idée, conçue, spécifiée, mise en œuvre, testée, documentée et envoyée aux utilisateurs.

Pour Java, un ou plusieurs éléments de cette liste ne se sont pas produits et vous ne disposez donc pas de cette fonctionnalité. Je ne sais pas lequel vous auriez à demander à un Java designer.

Pour C #, tout cela est arrivé - il y a environ quatorze ans maintenant - et le programme correspondant en C # a généré une erreur depuis le C # 1.0.

15
Eric Lippert

En plus de la réponse de arshajii, je souhaite montrer une dernière chose:

Ce n'est pas le assignation qui cause l'erreur mais simplement l'utilisation du littéral. Quand tu essaies

long i = 2147483648;

vous remarquerez que cela provoque également une erreur de compilation puisque le côté droit est toujours un int- littéral et hors de portée.

Ainsi, les opérations avec int- valeurs (y compris les affectations) risquent de déborder sans erreur de compilation (et également sans erreur d'exécution), mais le compilateur ne peut tout simplement pas gérer ces littéraux trop volumineux.

12
piet.t

A: Parce que ce n'est pas une erreur.

Background: La multiplication 1024 * 1024 * 1024 * 1024 entraînera un débordement. Un débordement est très souvent un bug. Différents langages de programmation produisent un comportement différent en cas de débordement. Par exemple, C et C++ appellent cela "comportement indéfini" pour les entiers signés et le comportement est défini comme un entier non signé (prenez le résultat mathématique, ajoutez UINT_MAX + 1 tant que le résultat est négatif, soustrayez UINT_MAX + 1 tant que le résultat est supérieur à UINT_MAX).

Dans le cas de Java, si le résultat d'une opération avec int valeurs ne se situe pas dans la plage autorisée, conceptuellement Java ajoute ou soustrait 2 ^ 32 jusqu'à ce que le résultat soit dans la Ainsi, la déclaration est tout à fait légale et non erronée, elle ne produit tout simplement pas le résultat escompté.

Vous pouvez certainement vous demander si ce comportement est utile et si le compilateur doit vous avertir. Personnellement, je dirais qu'un avertissement serait très utile, mais une erreur serait inexacte puisqu'il s'agit de Java légal.

4
gnasher729