begin process at 2008 05 16 07:33:35
1 173 219 membres
66 nouveaux aujourd'hui
13 970 membres club

Vous ne trouvez pas de réponse à votre problème ? Alors posez la question dans le forum.
Souvenez-vous qu'il n'y a jamais de question bête, mais rester dans l'ignorance parce que l'on n'ose pas poser une question, ça c'est une erreur !

JAVA ET LE BYTECODE : COMPRENDRE LE RÉSULTAT DE VOS COMPILATIONS


Information sur le tutorial

Catégorie :Systeme Date de création : 13/05/2005 20:56:00 Vu : 10 432 fois

Note :
8,5 / 10 - par 2 personnes
8,50 / 10

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

Commentaire sur cette source (4)
Ajouter un commentaire et/ou une note


Description

Voici un article qui vous introduira aux binaires Java, afin de pouvoir optimiser ou modifier votre code et peut-être, pour vous, de créer un mini compilateur Java, un obfuscateur ou un générateur de code à la volée !

Dans un premier temps, nous nous attarderons sur la machine virtuelle Java ou JVM (Java Virtual Machine), de cette façon, la compréhension des instructions binaires et de la structure d'un fichier .class sera plus facile à aborder par la suite. Cette première partie n'est rien de plus qu'un rassemblement des spécifications de la JVM, et un cours d'introduction sur le 'byte-code' (ce que l'on pourrait appeler l'assembleur du Java). Amusez-vous bien, c'est une nouvelle dimension pour ceux qui connaissent déjà le langage Java sur le bout des doigts !

Pour retrouver les spécifications de la machine virtuelle voici le lien : Site de SUN.

Si vous avez des suggestions, des questions, si vous dénotez des inexactitudes ou des imprécisions, ou pour tout autre commentaire, vous pouvez me contacter à cette adresse : neodante [At] neogamedev [.] com ou alors par message privé sur Codes sourceS.

Tutorial

Neodante (Julien CHABLE) - Version 1.11 - 13/05/2004

ATTENTION : Article de niveau avancé - nécessite une connaissance générale de Java

Le binaire Java

Lorsque vous compilez votre code Java à l'aide de javac par exemple, le code résultant de cette opération est appelé byte-code.Cecode compilé peut-être exécuté par n'importe quelle JVM répondantauxspécifications de SUN, de plus le format de ce code binaire estindépendant du matériel et de la plateforme sur laquelle il estexécuté. Le code peut-être, mais pas nécessairement, contenu dans unfichier (avec généralement l'extension .class). Le format de fichier class définit précisemment la représentation d'une classe ou d'une interface Java dans un fichier de ce type.

Révision et nouveautés

Les types

Lelangage de programmation Java, tout comme la machine virtuelle, opèresur 2 sortes de type : les types dit 'primitif' et les typesdit'référence'. De ce fait, une variable peut contenir ces 2 sortes detypes.
Une référence peut-être de plusieurs types : de type classe, de type tableau, et de type interface.

Parmi les types supportés par la JVM, nous pouvons distinguer les types numériques, les booléen et les types de retour d'adresse (returnAddress).

Les numériques

Les types numériques sont représentés par les types dit intégral et les types dit à virgule flottante. Les types intégrals sont :

  • byte, valeur entière sur 8 bits signée (complément à 2) de -128 à 127 inclus.
  • short, valeur entière sur 16 bits signée (complément à 2) de -32768 à 32767 inclus.
  • int, valeur entière sur 32 bits signée (complément à 2) de -2147483648 à 2147483647 inclus.
  • long, valeur entière sur 64 bits signée (complément à 2) de -9223372036854775808 à 9223372036854775807 inclus.
  • char, valeur entière sur 16 bits non signée représentant un caractère Unicode de 0 à 65535 inclus.
Les types à virgule flottante sont:
  • float, valeur à virgule flottante sur 32 bits à simple précision IEEE
  • long, valeur à virgule flottante sur 64 bits à double précision IEEE

Le booléen

Le type booléen :

  • boolean, encode une valeur de vérité true (vrai) ou false (faux)

Le retour d'adresse

Le type de returnAddress est un pointeur vers un des opcodes de la JVM (utilisé par les instructions jsr, ret, et jsr_w). Le type returnAddress n'est pas directement associé au langage de programmation Java.

Zone de données au runtime

LaJVM définit de nombreuses zones de données pendant le runtime,qui sontutilisées pendant l'exécution d'un programme. Certaines zones sontcréées au démarrage de la JVM et sont détruites seulement lorsquecelle-ci s'arrête. D'autres zones de données sont utilisées pourchaqueThread, elles sont créées lorsque le Thread est créé, etdétruites à ladestruction du Thread.

Afin de mieux comprendre lemécanisme d'exécution des byte-codes, nous allons voir rapidement cesdifférents espaces de stockage dedonnées.

Le registre pc

Lamachine virtuelle supporte plusieurs threads à la fois pendantl'exécution d'un programme. Chaque thread de la machine virtuelleJavapossède son propre pc (program counter). Nous n'entrerons pas plusdansles détails car le sujet dépasse l'objet de cet article.

La pile de la JVM

Chaque thread de la JVM possède une pile privée, créée en même temps que la Thread. Une pile de JVM stocke des frames (cf la partie sur les frames).

Le tas

Lamachine virtuelle Java possède un tas commun à toutes les threadsde lamachine virtuelle. La tas est l'espace mémoire depuis lequel toutes lesinstances de classes et les tableaux sont alloués.Le tas est créé audémarrage de la machine virtuelle Java.

Le stockage d'objet dans le tas est géré par un système de gestion de stockage automatique (connu sous le nom de garbage collector), de ce fait les objets ne sont jamais désalloués explicitement.

Zone de méthodes

LaJVM possède une zone de méthodes qui est partagée parmi tous lesthreads de celle-ci. Cette zone stocke les structures par classe.Chaquestructure comprend la constant pool, les champs, les donnéesdes méthodes, et le code pour les méthodes et les constructeurs,incluant les méthodes spéciales utilisées pour l'initialisation desclasses et des instances.

La constant pool du runtime

La constant pool est une représentation par classe ou par interface de la table constant_pool d'un fichier class (nousreviendrons ultérieurement dessus). Elle contient plusieurs sortes deconstantes, allant du litéral numérique connu à la compilation,jusqu'aux références de méthodes ou de champs qui doivent être résoluespendant le runtime.
Une constant pool est allouée dans la zone des méthodes, pour chaque classe ou interface créée par la machine virtuelle Java.

La pile des méthodes natives

Nous ne nous attarderons pas sur cette zone de données, le sujet dépassant l'objectif de cet article.

Les frames

Uneframe est utilisée pour stocker des données et des résultats partiels,tout comme pour effectuer les liaisons dynamiques (dynamic linking),retourner les valeurs des méthodes, et dispatcher les exceptions.

Unenouvelle frame est créée chaque fois qu'une méthode est invoquée, etest détruite lorsque l'invocation se termine. Une frame est allouée surla pile de la thread qui a créée cette frame. Ainsi chaque framepossède son propre tableau de variables locales, sa propre piled'opérandes, et une référence vers la constant pool de la classe de laméthode courante.

Seulement une frame, la frame de la méthode qui s'exécute, est active à un moment donné. La frame est identifiée comme la frame courante, et sa méthode est identifiée comme étant la méthode courante. La classe dans laquelle la méthode courante est définie s'appelle la classe courante.Lorsque l'on parlera d'opérations sur les variables locales et la piledes opérandes, ces opérations s'effectueront toujours sur ceux de laframe courante.

Une frame cesse d'être courante si sa méthodeinvoque une autre méthode ou si la méthode se termine (normalement oupas ->exception). Quand une méthode est invoquée, une nouvelle frameest créée et devient courante. Lors du retour d'une méthode, la framecourante passe le résultat de son invocation de méthode (la valeurderetour de la méthode), à la frame précédente, si elle existe. Laframe courante est alors détruite et la frame précédente redevient laframe courante.

Les variables locales

Chaque frame contient un tableau de variables connu sous le nom de variables locales.Unevariable locale simple peut contenir un type boolean, byte, char,short, int, float, reference, ou returnAddress. Une paire de variableslocales (soit 2 variables simples) peut contenir un type long ou double.

Lesvariables locales sont adressées par indexation. L'index de la premièrevariable local est 0. L'intervalle d'un index est donc [0;taille dutableau des variables locales - 1]. Une valeur de type long ou de type double occupe 2 variables locales consécutives.

La JVM utilise les variables locales pour passer des paramètres lors de l'invocation de méthode.
Lors de l'invocation d'une méthode de classe (méthode statique, cf Définitionsenfin d'article), tous les paramètres sont passés dans des variableslocales consécutives en partant de la variable locale d'index 0.
Lors de l'invocation d'une méthode d'instance (cf Définitions enfin d'article), la variable locale d'index 0 est toujours utilisée pourpasser une référence à l'objet à partir duquel cette instance deméthode a été invoquée (this en langage de programmation Java).
Tous les autres paramètres sont passés dans les variables locales consécutives en partant de l'index 1.

La pile des opérandes

Chaque frame contient une pile (Last In First Out) connue sous le nom de pile des opérandes.La pile des opérandes est vide quand la frame est créée. La JVM fournitdes instructions pour charger des constantes ou des valeurs, depuis lesvariables locales ou les champs, sur la pile des opérandes. D'autresinstructions prennent des opérandes de la pile des opérandes,effectuent une opération sur celles-ci, et remettent le résultat sur lapile des opérandes. Comme nous l'avons vu juste au-dessus avec lesvariables locales, la pile des opérandes est aussi utilisée pourpréparer les paramètres à passer aux méthodes, et pour recevoir lesrésultats de celles-ci.

Par exemple, l'instruction iadd additionne 2 valeurs de type int.Elle requiert que les valeurs int devant être additionnées, soit lesdeux valeurs au sommet de la pile des opérandes. Elles auront étéplacées ici par de précédentes instructions (action sur la pile ditde'push', par exemple l'instruction iload). Les deux valeurssont alors retirées de la pile (action dit de 'pop'), puisadditionnées, et leur somme est placée au sommet de la pile desopérandes (de nouveau un 'push').

Pile début -> instruction -> Pile fin
..., val1, val2 -> xadd -> ..., result

Préparation aux instructions de la JVM

Une instruction de la machine virtuelle Java consiste en un opcode sur1 byte (1 octet) spécifiant l'opération à effectuer, suivie par zéro ouplusieurs opérandes servant d'arguments ou de données qui serontutilisés par l'opération. Plusieurs instructions n'ont pas d'opérandeet consistent donc simplement en un opcode.

Voici un pseudo-code de l'exécution du byte-code :

do {
   fetch an opcode;
   if (operands) fetch operands;
      execute the action for the opcode;
} while (there is more to do);

Le nombre, le type et la taille des opérandes sont déterminés par l'opcode.

Les types de la JVM

Laplupart des instructions de la JVM 'encodent' les informations de typeà propos de l'opération qu'elles effectuent dans leur nom. Par exemple,l'instruction iload charge le contenu d'une variable locale, qui doit être un int, au dessus de la pile des opérandes. L'instruction floadfait de même pour une value de type float. Plusieurs instructionspeuvent avoir la même fonction, mais des opcodes différents;parexemple, c'est le cas de iload, fload, dload, ..., tous chargent une variable locale sur la pile des opérandes.

Decette façon, pour la majorité des instructions typées, le type del'instruction est représenté explicitement dans le nom de l'opcode parune lettre : i pour une opération sur un int, l pour une opération sur un long, s pour une opération sur un short, b pour une opération sur un byte, c pour une opération pour un char, f pour une opération pour un float, d pour une opération sur un double, et enfin a pour une opération sur une référence.
Quelquesinstructions pour lesquelles le type n'est pas ambigue (c'est à direqu'un seul type est autorisé, par exemple les opcodes putfield, jsr, ...) n'ont pas de lettre spécifique dans leur nom.

Lalongueur de 1 byte (1 octet -> 8 bits) d'un opcode empêche d'avoirun jeu d'instructions supérieur à 256 opcodes. Par conséquent ce faiblenombre d'instructions réduit le nombre de types supportés pourcertaines opérations (on ne va pas trouver toutes les instructions dujeu d'instructions pour chaque type de données). En d'autres termes, lejeu n'est pas orthogonal, des instructions supplémentaires peuvent êtreutilisées pour convertir les types de données non supportés en types dedonnées supportés. Cela peut réduire les performances dans les codes depersonnes non initiées, mais cela est nécessaire pour garder un jeud'instructions et des fichiers class compacts.

Le tableausuivant résume le support des types des instructions de la JVM. Uneinstruction spécifique, avec l'information de type, est construite enremplaçant le T dans la colonne des opcodes par la lettre du type de lacolonne. Si la colonne de type pour certains modèles d'instructions estblanche, cela signifie qu'il n'existe pas de support pour ce typeconcernant cette opération. Par exemple, il y a une instruction dechargement (Tload) pour le type int (iload), mais paspour le type byte.

La plupart des opérations ne supportent pas les types boolean, byte, char, et short. Par conséquent, les valeurs de ces types, sont implicitement converties en type int à la compilation ou à l'exécution, et ensuite exécutées par des instructions de type int. Regardez les spécifications pour plus de détails.

opcode byte short int long float double char reference
Tipush bipush sipush            
Tconst     iconst lconst fconst dconst   aconst
Tload     iload lload fload dload   aload
Tstore     istore lstore fstore dstore   astore
Tinc     iinc          
Taload baload saload iaload laload faload daload caload aaload
Tastore bastore sastore iastore lastore fastore dastore castore aastore
Tadd     iadd ladd fadd dadd    
Tsub     isub lsub fsub dsub    
Tmul     imul lmul fmul dmul    
Tdiv     idiv ldiv fdiv ddiv    
Trem     irem lrem frem drem    
Tneg     ineg lneg fneg dneg    
Tshl     ishl lshl        
Tshr     ishr lshr        
Tushr     iushr lushr        
Tand     iand land        
Tor     ior lor        
Txor     ixor lxor        
i2T i2b i2s   i2l i2f i2d    
l2T     l2i   l2f l2d    
f2T     f2i f2l   f2d    
d2T     d2i d2l d2f      
Tcmp       lcmp        
Tcmpl         fcmpl dcmpl    
Tcmpg         fcmpg dcmpg    
if_TcmpOP     if_icmpOP         if_acmpOP
Treturn     ireturn lreturn freturn dreturn   areturn

Certaines instructions de la JVM comme pop et swapopèrent sur la pile des opérandes sans faire attention à leur type ;cependant, de telles instructions sont contraintes d'être utiliséesavec des valeurs d'une certaine catégorie, donnée dans letableauci-dessous :

Type Type calculé Categorie
boolean int 1
byte int 1
char int 1
short int 1
int int 1
float float 1
reference reference 1
returnAddress returnAddress 1
long long 2
double double 2

Par exemple, les instructions pop et dup sont utilisées pour les types de la catégorie 1, et les instructions pop2 et dup2sont utilisées pour les types de la catégorie 2. On comprendra aisémentque les types ayant une taille de 64 bits (8 octets) appartiennent à lacatégorie 2.

Charger et stocker des instructions

Lesinstructions de chargement et de stockage transfèrent les valeurs entreles variables locales et la pile d'opérandes d'une frame de la JVM :

  • Charge une variable locale sur la pile des opérandes : iload, iload_N, lload, lload_N, fload, fload_N, dload, dload_N, aload, aload_N.
  • Stocke une valeur depuis la pile des opérandes vers une variable locale : istore, istore_N, lstore, lstore_N, fstore, fstore_N, dstore, dstore_N, astore, astore_N.
  • Charge une constante sur la pile des opérandes : bipush, sipush, ldc, ldc_w, ldc2_w, aconst_null, iconst_m1, iconst_N, lconst_N, fconst_N, dconst_N.
  • Avoiraccèsà plus de variables locales en utilisant un index plus grand, oua uneopérande immédiate plus large (par exemple, avoir un long au lieu de unint) : wide.

Les instructions qui accèdent auxchamps des objects et aux éléments des tableaux, transfèrent aussi desdonnées depuis et vers la pile desopérandes.

Le format des instructions avec des lettres génériques N (par exemple, iload_N)dénote des familles d'instructions (avec les membres iload_0, iload_1,iload_2 et iload_3 dans le cas de iload_N). De telles famillesd'instructions sont des spécialisations d'une instruction générique(iload) qui ne prend qu'un paramètre. Pour les instructions génériques,l'opérande est implicite et l'instruction iload_0 signifie la même chose que iload.

Les instructions arithmétiques

Lesinstructions arithmétiques calculent un résultat, ce sont typiquementdes fonctions qui prennent 2 valeurs sur la pile des opérandes, et quimettent le résultat au-dessus de cette même pile une fois l'opérationeffectuée. Il existe 2 types principaux d'instructions arithmétiques :ceux opérant sur des valeurs entières et ceux opérant sur des valeurs àvirgule flottante. Comme nous l'avons vu un peu plus haut, il n'existepas de support direct pour une arithmétique entière sur les types byte, short, char, et boolean; ces opérations sont gérées par les instructions opérant sur le type int. Les instructions arithmétiques sont les suivantes :

  • Addition : iadd, ladd, fadd, dadd.
  • Soustraction : isub, lsub, fsub, dsub.
  • Multiplication : imul, lmul, fmul, dmul.
  • Division : idiv, ldiv, fdiv, ddiv.
  • Reste (modulo) : irem, lrem, frem, drem.
  • Négation : ineg, lneg, fneg, dneg.
  • Shift (décalage de bit) : ishl, ishr, iushr, lshr, lshl, lushr.
  • OR (bit à bit) : ior, lor.
  • AND (bit à bit) : iand, land.
  • XOR (bit à bit) : ixor, lxor.
  • Incrémentation : iinc.
  • Comparaison : dcmpg, dcmpl, fcmpg, fcmpl, lcmp.

Lesinstructions entière et à virgule flottante diffèrent aussi dans leurcomportement pour ce qui est de l'overflow et la division parzéro. Lesopérations sur les types entiers ne renvoient pas d'overflow; la seuleerreur pour ces opérations (idiv, ldiv, irem, lrem) est la division parzéro qui lance un ArithmeticException (cf le paragraphesur lesexceptions en fin d'article).

Les comparaisons sur les valeurs de type long (lcmp) effectue une comparaison signée. Les comparaisons sur les types à virgule flottante (dcmpg, dcmpl, fcmpg, fcmpl) sont effectuées en utilisant la comparaison IEEE 754.

Instructions de conversion de types

Lesinstructions de conversion de types permettent la conversion entre lesdifférents types numériques de la JVM. Celles-ci peuvent être utiliséespour implémenter une conversion explicite. La conversion d'une valeurd'un type vers un autre type possédant un intervalle de valeurs pluspetit, nécessite une conversion explicite. Par exemple la conversiond'un long vers un int.
Voici les différentes conversions réalisables (implicites et explicites) par la JVM :

La JVM supporte directement les conversions d'élargissement d'intervalle suivantes :

  • int vers long, float ou double
  • long vers float ou double
  • float vers double

Les instructions de conversion numerique d'élargissement sont i2l, i2f, i2d, l2d, et f2d. La signification de ces opcodes est relativement simple à comprendre. Par exemple, l'instruction i2f convertie une valeur de type int vers une valeur de type float.

Anoter que la conversion numérique d'élargissement est automatique pourle langage de programmation Java (le programmeur n'a pas besoin de lefaire explicitement). Egalement, la conversion n'existe pas pour lestypes byte, char, et short vers le type int. Car comme spécifié plushaut, les valeurs de type byte, char, et short sont élargiesintrinsèquement vers le type int, par une conversion implicite.(Rappel: la conversion d'un type numérique vers un booléen n'est pasautorisé en Java.)

La JVM supporte également les conversions numériques de rétrécissement d'intervalles :

  • int vers byte, short ou char
  • long vers int
  • float vers int ou long
  • double vers int, long ou float

Les instructions de conversion numérique de 'rétrécissement' sont i2b, i2c, i2s, l2i, f2i, f2l, d2i, d2l, et d2f.Les règles de conversion sont celles du langage de programmationJava.Une conversion de ce type peut entraîner un résultat ayant unsigne différent de la valeur initiale, et/ou une perte de précision,mais cela reste pour des cas particuliers. Veuillez vous reporter aux spécifications pour de plus amples informations sur les erreurs de conversion.

Manipulation et création d'objet

Bienque les instances de classe et les tableaux soient des objets, la JVMcrée et manipule les instances de classe et les tableaux de façondistincte avec un jeu d'instructions propre à chacun :

  • Création d'une nouvelle instance de classe : new.
  • Création d'un nouveau tableau : newarray, anewarray, multianewarray.
  • Accèsauxchamps d'une classe (champs statiques, aussi appelés variablesdeclasse) et les champs d'instance de classe (champs non statiques,aussi appelés variables d'instance) : getfield, putfield, getstatic, putstatic
  • Chargement d'un élément d'un tableau sur la pile des opérandes : baload, caload, saload, iaload, laload, faload, daload, aaload.
  • Stocker une valeur depuis la pile des opérandes en tant qu'élement de tableau : bastore, castore, sastore, iastore, lastore, faastore, dastore, aastore.
  • Obtenir la longueur d'un tableau : arraylength
  • Vérifier les propriétés d'instances de classe ou de tableaux : instanceof, checkcast.

Les instructions de gestion de la pile des opérandes

Un certain nombre d'instructions est fourni pour la manipulation directe de la pile des opérandes : pop, pop2, dup, dup2, dup_x1, dup2_x1, dup_x2, dup2_x2, swap.

Les instructions de controle

Lesinstructions de branchements conditionnels ou inconditionnelspermettent à la JVM de continuer de s'exécuter avec une instructiondifférente de celle suivant l'instruction de branchement. Lesinstructions de branchement sont :

  • Branchement conditionnel : ifeq,iflt, ifle, ifne,ifgt, ifge, ifnull, ifnonnull, if_icmpeq, if_icmpne,if_icmplt,if_icmpgt, if_icmple, if_icmpge, if_acmpeq, if_acmpne.
  • Branchement conditionnel composé : tableswitch, lookupswitch.
  • Branchement inconditionnel : goto, goto_w, jsr, jsr_w, ret.

Toutes ces comparaisons sont effectuées en tenant compte du signe.

Instructions d'invocation de méthode et retour

Les instructions suivantes invoquent une méthode :

  • invokevirtualinvoque une méthode d'une instance d'un objet, en appelant la bonneméthode virtuelle de l'objet. Ceci est le comportement normal dulangage de programmation Java.
  • invokeinterface invoqueune méthode qui est implémentée par une interface, en cherchant lesméthodes implémentées par cet objet pour trouver la méthode appropriée.
  • invokespecialinvoque une instance qui requiert un traitement special, c'est à direune méthode d'initialisation d'instance, une méthode privée, ou uneméthode de la super classe.
  • invokestatic invoque une méthode de classe (statique) dans la classe nommée.

Les instructions de retour de méthodes, qui sont distinguables par le type de retour, sont ireturn (utilisée pour retourner des valeurs de type boolean, byte, char, short, ou int), lreturn, freturn, dreturn, et areturn. De plus, l'instruction returnest utilisée pour retourner depuis des méthodes déclarées void, desméthodes d'initialisation d'instance, et des méthodes d'initialisationde classe ou d'interface.

Lancement d'exception

Une exception est lancée 'programmaticalement' en utilisant l'instruction athrown.Les exceptions peuvent aussi être lancées par d'autres instructions dela JVM lorsqu'une condition inhabituelle est détectée (par exemple, ladivision par zéro).

Définitions :

Méthode d'instance :une sous-routine ou une fonction appartenant à l'objet courant. Lesméthodes font toujours parties d'une classe en Java, vous ne pouvez pasavoir de méthodes seules comme le permet le C ou le C++. Une méthoded'instance a accès à toutes les variables de l'instance, à toutes lesautres méthodes d'instances, et aux méthodes et variables statiques.

Méthode de classe :une méthode statique dans uneclasse. Elle n'a pas accès aux variablesd'instance, seulement auxvariables statiques de cette classe. De plus,elle ne peut pas invoquerde méthode d'instance (non statique) à moinsqu'elle ne possède uneréférence vers cet objet.

14 mai 2005 10:31:39 :
Correction de la conversion Word -> FreeTextBox (petit bug d'espace entres les mots)
  • signaler à un administrateur
    Commentaire de bjdc le 14/05/2005 09:26:10

    Excellent document qui nous en apprend un peu plus sur le fonctionnement de la JVM :)

  • signaler à un administrateur
    Commentaire de Mr.X le 16/12/2006 12:05:25

    Je vien ptete trop du monde C/C++ mais un char c'est pas sur 8bits normalement ?

  • signaler à un administrateur
    Commentaire de neodante le 16/12/2006 12:54:41 administrateur CS

    Le Java ou le .NET (les machines virtuelles en général, surtout avec les normes Unicode récente, le C/C++ commençant à accuser le poids des années dans ce contexte) ne sont pas forcément identique ... un char doit pouvoir encoder plus qu'un simple caractère ascii ... le byte est quand à lui sur 8 bits ;-)

  • signaler à un administrateur
    Commentaire de verdy_p le 08/04/2008 23:38:39

    Le char est un type entier 16 bits non signé (positif ou nul); ce n'est pas un caractère au sens propre, même s'il contient couramment un unique point de code Unicode: en effet c'est bien un type arithmétique, et d'ailleurs la JVM le convertit implicitement en entier 32 bits par ajout de bits nuls (puisqu'il est toujours positif) lors du chargement d'un char sur la pile. Il est donc proche du type short (16 bits signés: la différence est que le short est converti implicitement sur la pile d'opérandes en int 32 bits par extension de signe).

    Un char ne permet pas forcément de stocker un caractère Unicode complet: il en faut deux contenant successivement un "surrogate" haut (entre 0xD800 et 0xDBFF) pour sélectionner une page de 4096 points de codes supplémentaires et un "surrogate" bas (entre 0xDC00 et 0xDFFF) pour sélectionner un des 4096 points de code dans cette page (ensembles, il sélectionne un des 2^20 points de codes supplémentaires d'Unicode hors du plan multilingue de base, alias BMP, d'Unicode.

    De plus le char ne done aucune contrainte de codage: il est permis de ne pas associer les surrogates par paire et dans l'ordre, et également permis d'utiliser la totalité des 2^16 valeurs possibles (donc y compris les valeurs 0xFFFE et 0xFFFF, qui correspondent aux points de code Unicode U+FFFE et U+FFFF, qui ne sont PAS des caractères Unicode valides, mais seulement des points de code).

    Les chaines java (java.lang.String) stockent en interne un tableau de "char". Elles ne sont donc pas contraintes par les restrictions d'Unicode, et donc les surrogates ne sont pas forcément stockés par paires et dans l'ordre. De fait, les chaines Java ne stockent pas que du texte Unicode valide (d'ailleurs elles pourraient être utilisées pour stocker n'importe quel autre codage qu'Unicode). Cependant, la classe wrapper "java.lang.Char" (qui contient un seul char en interne) fournit des méthodes statiques permettant d'accéder à pas mal de propriétés et algorithmes Unicode standards (au moins pour les caractères valides du BMP), y compris pour gérer le codage Unicode des points de code supplémentaires (hors du BMP) en les décomposant en surrogates.

    Quant au "byte" de Java, s'il est effectivement stocké sur 8 bits, ce n'est pas le cas quand on le charge sur la pile: il est automatiquement étendu en int sur 32 bits par extension de signe. Le "byte" Java est signé (valeurs entre -128 et +127). Pour utiliser des octets non signés, il faut les lire et utiliser un masquage avec l'opérateur "&" (par exemple: System.out.println("octet = " + (b & 0xFF)). Ca parait pénible mais on s'y fait vite (il aurait été pratique que le langage sache compiler et comprendre naturellement les octets non signés sans avoir à écrire les masquages de bits nécessaires; cependant cela aurait posé un problème avec le polymorphisme, car la JVM ne saurait pas différencier à l'exécution les méthodes utilisant un "byte" et un octet non signé dans leur signature.

    On peut noter que le bytecode ne fournit pas d'instruction spécifique pour effectuer ce masquage (il faut donc utiliser bload pour charger un byte sur la pile, mais il est aussitôt converti en int 32-bits signés) puis ensuite masuer les bits indésirables pour les calculs. Le bytecode est alors exécuté par la JVM avec les opérations arithmétiques sur 32 bits.

    Ceci dit si on accepte que la JVM ne sache pas faire la différence à l'exécution entre deux méthodes surchargées utilisant des types signés ou non signés, et que donc le compilateur rejette deux méthodes qui ne se différencient que par le fait que les entiers sont déclarés signés ou non mais alors que la JVM ne peuvent en accepter qu'une seule à la fois (sinon il y aurait homonymie parfaite des méthodes), on peut concevoir un langage autre que le Java standard, qui puisse se compiler en étant compatible avec la JVM, mais fase la différence entre les types. Il faudra que le compilateur lève l'ambiguité en renommant différemment les méthodes, mais on ne pourra pas facilement retrouver à l'exécution par Reflection les types déclarés. Et on aura de toute façon à compiler le bytecode qui effectue le masquage lors de la lecture des champs d'objets ou élements de tableaux utilisant ces types non standards. (Il existe de tels langages étendus, créés pour faciliter le portage par exemple depuis des sources .Net, ils peuvent utiliser les annotations de Java 5+ pour connaitre les noms réels de méthodes surchargées et les types réels déclarés dans ce langage étendu).

    Dernier point: la JVM ne se contente pas de lire le bytecode octet par octet pour l'interpréter: il maintient des statistiques sur le nombre de fois où une même méthode est appelée, et au delà d'un seuil (paramétrable par l'utilisateur dans les paramètres de la JVM), il compile ces méthodes automatiquement en code natif. Ce compilateur "HotSpot" est très performant et comme il tient compte des statistiques d'utilisation du code, is sait optimiser non seulement les appels mais aussi déplacer dans le code natif compilé le code correspondant au bytecode moins fréquent pour réduire le nombre de branchements et simplifier les opérations. Dans bien des cas, cette compilation en code natif à l'exécution sera bien meilleure que ce que pourrait produire un compilateur C classique car la compilation sera guidée par le profilage effectif de l'application, telle qu'elle est réellement utilisée. Ca explique grandement les excellentes performances de Java par rapport au C/C++ (on a des benchmarks qui effectivement montrent que le même code source Java peut être même plus rapide que le C ou C++, et notemment dans les applications multithread. HotSpot sait même remplacer des appels de méthodes par une inclusion inline dans se baser sur des critères approximatifs basés sur des a-priori comme la taille maximale des méthodes (ce que font les compilateurs C/C++). C'est tellemetn vrai que des compilateurs C/C++ compilent maintenant le source dans un Bytecode au lieu d'un code natif, et utilisent alors un appel de librairie contenant une sorte de VM (effectuant la partie finale de la compilation en code natif seulement à l'exécution), avec également d'autres avantages en terme de sécurité (code managé et vérifiable à l'exécution): cela ressemble à ce que faisait les premiers compilateurs qui généraient du "P-Code" à partir de sources Pascal ou Cobol (sauf que ce P-Code était exécuté par une librairie native qui ne savait pas le compiler en code natif mais se contentait de l'interpréter octet par octet; on n'en est plus là! Les compilateurs, alias "JIT", qui créent le code natif à l'exécution et guidés par le profilage, ont d'énormes avantages en terme tant de sécurité que de performance). L'étape suivante sera sans doute du code natif capable de s'automodifier, si les processeurs savent maintenir eux-même les statistiques de profilage du code natif et notemment les statistiques de prédiction de branchement et de gestion deleurs pipelines parallèles sans que ce travail leur soit prémâché "à priori" par les compilateurs, même ceux de type JIT.

Ajouter un commentaire

Appels d'offres

Pub



CalendriCode

Mai 2008
LMMJVSD
   1234
567891011
12131415161718
19202122232425
262728293031 

Boutique

Boutique de goodies CodeS-SourceS