Question Pourquoi le compilateur ne génère-t-il pas d'erreur pour cette opération d'addition? [dupliquer]


Cette question a déjà une réponse ici:

Je sais que le compilateur effectue une conversion de type implicite pour les littéraux entiers. Par exemple:

byte b = 2; // implicit type conversion, same as byte b = (byte)2;

Le compilateur me donne une erreur si la plage déborde:

byte b = 150; // error, it says cannot convert from int to byte

Le compilateur donne la même erreur lorsque la variable est passée à une expression:

byte a = 3;
byte b = 5;
byte c = 2 + 7; // compiles fine
byte d = 1 + b; // error, it says cannot convert from int to byte
byte e = a + b; // error, it says cannot convert from int to byte

Je suis arrivé à la conclusion que le résultat d'une expression impliquant des variables ne peut être garanti. La valeur résultante peut être comprise dans ou en dehors de la plage d'octets, le compilateur rejette une erreur.

Ce qui me dérange, c'est que le compilateur ne jette pas d'erreur quand je le mets comme ceci:

byte a = 127;
byte b = 5;
byte z = (a+=b); // no error, why ?

Pourquoi ça ne me donne pas une erreur?


30
2018-02-22 06:07


origine


Réponses:


La réponse est fournie par JLS 15.26.2:

Par exemple, le code suivant est correct:

short x = 3;

x += 4.6;

et résulte en x ayant la valeur 7 car il est équivalent à:

short x = 3;

x = (short)(x + 4.6);

Ainsi, comme vous pouvez le voir, le dernier cas fonctionne, car l’affectation d’ajout (comme toute autre attribution d’opérateur) effectue un transtypage implicite dans le type de main gauche (et dans votre cas a est un byte). Étendre, c'est équivalent à byte e = (byte)(a + b);, qui compiler heureusement.


5
2018-02-22 15:10



Tout en décompilant votre code va expliquer quelle Java fait, la raison Pourquoi cela se fait généralement dans la spécification du langage. Mais avant de commencer, nous devons établir quelques concepts importants:

Donc, nous revenons à ce scénario: pourquoi ajouter deux octets qui sont clairement plus que ce qu'un octet peut gérer ne produit pas une erreur de compilation?

Il ne déclenchera pas une exception d'exécution en raison de débordement.

C'est le scénario dans lequel deux nombres ajoutés ensemble produisent soudainement un très petit nombre. En raison de la petite taille de bytela gamme, il est extrêmement facile de déborder; par exemple, l'ajout de 1 à 127 le ferait, ce qui donnerait -128.

La principale raison pour laquelle il va envelopper est due à la façon dont Java gère la conversion des valeurs primitives; dans ce cas, nous parlons de une conversion plus étroite. C'est-à-dire, même si la somme produite est plus grand que byte, la conversion restreinte entraînera le rejet des informations pour permettre aux données byte, car cette conversion ne provoque jamais une exception d'exécution.

Pour décomposer votre scénario étape par étape:

  • Java ajoute a = 127 et b = 5 ensemble pour produire 132.
  • Java comprend que a et b sont de type byte, donc le résultat doit aussi être de type byte.
  • Le résultat de l'entier est toujours de 132, mais à ce stade, Java effectuera un transtypage pour limiter le résultat à un octet - ce qui vous donnera (byte)(a += b).
  • Maintenant, les deux a et z contient le résultat -124 en raison du bouclage.

22
2018-02-22 06:51



Je suis arrivé à la conclusion que le résultat d'une expression impliquant des variables ne peut être garanti. La valeur résultante peut être comprise dans ou en dehors de la plage d'octets, le compilateur rejette une erreur.

Non, ce n'est pas la raison. Les compilateurs d'un langage de type statique fonctionnent de la manière suivante: toute variable doit être déclarée et typée, donc même si sa valeur n'est pas connue à la compilation, son type est connu. La même chose vaut pour les constantes implicites. Sur la base de ce fait, les règles de calcul des échelles sont essentiellement les suivantes:

  • Toute variable doit avoir la même échelle ou une échelle supérieure à celle de son côté droit.
  • Toute expression a la même échelle que le terme maximal impliqué.
  • Un jet de forces explicite, de corse, l'échelle de l'expression du côté droit.

(Celles-ci sont en fait une vue simplifiée, peut-être même un peu plus complexe).

Appliquez-le à vos cas:

byte d = 1 + b

Les échelles réelles sont les suivantes:

byte = int + byte

... (car 1 est considéré comme implicite int constant). Donc, en appliquant la première règle, la variable doit avoir au moins int échelle.

Et dans ce cas:

byte z = (a+=b);

Les échelles réelles sont les suivantes:

byte = byte += byte

... ce qui est correct

Mettre à jour

Alors pourquoi byte e = a + b produire une erreur de compilation?

Comme je le disais, les règles de type réelles dans java sont plus complexes: alors que les règles générales s'appliquent à tous les types, la primitive byte et short les types sont plus restreints: le compilateur suppose que l'ajout / la soustraction de deux octets / courts ou plus risque de provoquer un débordement (comme indiqué par @Makoto), il doit donc être stocké comme le type suivant d'échelle considéré comme plus sûr: int.


5
2018-02-22 06:51



La raison de base est que le compilateur se comporte un peu différemment lorsque des constantes sont impliquées. Tous les littéraux entiers sont traités comme int les constantes (sauf si elles ont un L ou l à la fin). Normalement, vous ne pouvez pas affecter un int à un byte. Cependant, il existe une règle spéciale où les constantes sont impliquées; voir JLS 5,2. Fondamentalement, dans une déclaration comme byte b = 5;, 5 est un int, mais il est légal de procéder à la conversion byte  car  5 est une constante et car il rentre dans la gamme de byte. C'est pourquoi byte b = 5 est autorisé et byte b = 130 n'est pas.

cependant, byte z = (a += b); est un cas différent a += b ajoute juste b à a, et renvoie la nouvelle valeur de a; cette valeur est affectée à a. Depuis a est un octet, il n'y a pas de conversion étroite impliquée - vous attribuez un octet à un octet. (Si a étaient un int, le programme serait toujours illégal.)

Et les règles disent que a + b (et donc a = a + b, ou a += b) ne débordera pas. Si le résultat, lors de l'exécution, est trop grand pour un octet, les bits supérieurs sont simplement perdus - la valeur est contournée. En outre, le compilateur ne "va pas suivre" pour remarquer que a + bserait plus grand que 127; même si nous peut dire que la valeur sera supérieure à 127, le compilateur ne gardera pas la trace des valeurs précédentes. A sa connaissance, quand il voit a += b, il sait seulement que le programme ajoutera b à a quand il fonctionne, et il ne regarde pas les déclarations précédentes pour voir quelles seront les valeurs. (Un bon compilateur optimisant pourrait en fait faire ce genre de travail. Mais nous parlons de ce qui rend un programme légal ou non, et les règles relatives à la légalité ne concernent pas l'optimisation.)


4
2018-02-22 06:40



Je l'ai déjà rencontré dans un projet et c'est ce que j'ai appris:

Contrairement à c / c ++, Java utilise toujours des primitives signées. Un octet va de -128 à +127, donc si vous affectez quelque chose derrière cette plage, cela vous donnera une erreur de compilation.

Si vous convertissez explicitement en octet comme (byte) 150 vous n'obtiendrez toujours pas ce que vous voulez (vous pouvez vérifier avec le débogueur et voir qu'il sera converti en autre chose).

Lorsque vous utilisez des variables comme x = a + b parce que le compilateur ne connaît pas les valeurs au moment de l'exécution et ne peut pas calculer si -128 <= a+b <= +127 cela donnera une erreur.

En ce qui concerne votre question sur pourquoi le compilateur ne donne pas d'erreur sur quelque chose comme a+=b :

Je creuse dans le compilateur Java disponible à partir de openjdk à

http://hg.openjdk.java.net/jdk9/jdk9/langtools.

J'ai suivi le traitement de l'arbre des opérandes et j'ai trouvé une expression intéressante dans l'un des fichiers du compilateur. Lower.java qui est en partie responsable de la traversée de l'arbre du compilateur. voici une partie du code qui serait intéressante (Assignop est pour tous les opérandes comme + = - = / = ...)

public void visitAssignop(final JCAssignOp tree) {
                        ...
                        Symbol newOperator = operators.resolveBinary(tree,
                                                                      newTag,
                                                                      tree.type,
                                                                      tree.rhs.type);
                        JCExpression expr = lhs;
                        //Interesting part:
                        if (expr.type != tree.type)
                            expr = make.TypeCast(tree.type, expr);
                        JCBinary opResult = make.Binary(newTag, expr, tree.rhs);
                        opResult.operator = newOperator;:

                        ....

comme vous pouvez voir si le rhs a un type différent du lhs, le type cast aurait lieu donc même si vous déclarez float ou double à droite (a+=2.55) vous n'obtiendrez aucune erreur en raison du type de distribution.


4
2018-02-22 08:22



/*
 * Decompiled Result with CFR 0_110.
 */
class Test {
    Test() {
    }

    public static /* varargs */ void main(String ... arrstring) {
        int n = 127;
        int n2 = 5;
        byte by = (byte)(n + n2);
        n = by;
        byte by2 = by;
    }
}

Après décompilation de votre code

class Test{
public static void main(String... args){
byte a = 127;
byte b = 5;
byte z = (a+=b); // no error, why ?
}
}

En interne, Java a remplacé votre a+=b opérateur avec (byte)(n+n2) le code.


0
2018-02-22 06:36



L'expression byte1+byte2 est équivalent à (int)byte1+(int)byte2, et a le type int. Alors que l'expression x+=y; serait généralement équivalent à var1=var1+var2;, une telle interprétation rendrait impossible l'utilisation += avec des valeurs inférieures à int, donc le compilateur traitera byte1+=byte2 comme byte1=(byte)(byte1+byte2);.

Notez que le système de type Java a été conçu avant tout pour la simplicité, et que ses règles ont été choisies pour avoir un sens dans de nombreux cas, mais parce que rendre les règles simples était plus important que de les rendre cohérentes, les règles produisent un comportement absurde. L'un des plus intéressants est illustré par:

long l1 = Math.round(16777217L)
long l2 = Math.round(10000000000L)

Dans le monde réel, on n'essayerait évidemment pas de contourner les constantes longues, mais la situation pourrait se présenter si quelque chose comme:

long distInTicks = Math.round(getDistance() * 2.54);

ont été modifiés pour éliminer le facteur d'échelle [et getDistance () renvoyé long]. Selon vous, quelles valeurs devraient recevoir l1 et l2? Pouvez-vous comprendre pourquoi ils pourraient recevoir une autre valeur?


0
2018-02-22 18:43