javaseiten.de   |   Version 0.6
 

5.5. Methoden

Eine Methodendefinition wird mit der Direktive .method eingeleitet, wobei direkt im Anschluss die Modifikatoren für die Methode, der Methodenname und der Deskriptor der Methode angeordnet werden. Der Methodendeskriptor repräsentiert die Parameter und den Rückgabewert der Methode. Mit .end method wird die Definition abgeschlossen.

Methodendefinition:

.method <methodmodifiers> <methodname><methoddescriptor>
  <statements>
.end method

Der Methodenkörper wird mit sogenannten Statements ausgefüllt, wobei Direktiven, Labels und die Instruktionen der virtuellen Java-Maschine als Statements gelten. Bestimmte Direktiven sind nur innerhalb des Methodenkörpers zulässig, die demnach auch als Methodendirekiven bezeichnet werden. Sie sollen im Anschluss näher erläutert werden.

Methodendirektiven: .limit stack, .limit locals, .catch, .throws, .line, .var

 

Die Methodendirektiven .limit stack und .limit locals

Die Methodendirektiven .limit stack und .limit locals stehen am Anfang des Methodenkörpers und werden benötigt um die maximale Größe des Operandenstatpels und die maximale Anzahl der Einträge im Array der lokalen Variablen festzulegen. Ein Aufruf einer Methode hat zur Folge, dass für diese Methode ein separater Operandenstapel und ein Array für lokale Variablen angelegt wird. Die JVM-Instruktion iadd, die zwei Ganzzahlen addiert benötigt z.B. einen Operandenstapel der Größe 2 für die beiden Operanden, die vor der Addition auf dem Stapel bereitgestellt werden. Die Größe das Arrays der lokalen Variablen hängt u.a. von den lokalen Variablen, die innerhalb der Methode verwendet werden, und von der Anzahl der Methodenparameter ab.

.limit stack <integer>
.limit locals <integer>

Die beiden oberen Schranken des Stapels und des Arrays werden hinter den jeweiligen Direktiven notiert, wobei für die Angabe <integer> eine positive Ganzzahl (einschließlich 0) gewählt werden kann.

 

Beispiel: Klassendefinition mit leerem Klassenkörper

Als Beispiel soll an dieser Stelle der folgende Java-Quelltext einer Klasse mit leerem Klassenkörper auf den zughörigen Jasmin-Quelltext abgebildet werden.

/* EmptyClass.java */

public class EmptyClass {

}

Die Klassendatei, die mit der Compilierung von EmptyClass.java mittels javac erhalten wird, könnte ebenfalls aus dem nachfolgenden Listing bei der Verwendung von Jasmin erzeugt werden.

Listing 5.2. EmptyClass.j. Der Jasmin-Quelltext enthält einen minimalen Klassenkörper, der lediglich den notwendigen Default-Konstruktor enthält.

; EmptyClass.j

.bytecode 50.0
.class public EmptyClass
.super java/lang/Object

.method public <init>()V
  .limit stack 1
  .limit locals 1
  aload_0
  invokespecial java/lang/Object/<init>()V
  return
.end method

Dabei wird deutlich, dass EmptyClass.j den Default-Konstruktor als einzige Methode explizit enthalten muss (der Default-Konstruktor wird durch javac EmptyClass.java automatisch in die zugehörige Klassendatei geschrieben). Der Operandenstapel des Konstruktors wird mittels .limit stack auf 1 begrenzt. Ein Stapel der Größe 1 reicht aus, da lediglich mit aload_0 eine Referenz aus dem Array der lokalen Variablen bei Index 0 auf den Operandenstapel geladen wird. Diese Referenz wird automatisch durch entprechende interne Abläufe der virtuellen Java-Maschine als erstes Element in das Array der lokalen Variablen geschrieben (die Begrenzung des Arrays durch .limit locals 1 reicht daher aus). Diese Referenz wird durch die JVM-Instruktion invokespecial vom Stapel genommen, um den Default-Konstruktor der Superklasse Object aufzurufen. Die eben genannte Referenz führt letztendlich zu der Zeichenkette java/lang/Object/<init>()V direkt nach invokespecial im Jasmin-Quelltext. Der eben genannte Zusammenhang kann durch einen Blick in die korrespondierende Klassendatei EmptyClass.class erklärt werden (Umkehrabbildung zu Jasmin-Quelltext --> Klassendatei): Die Instruktion invokespecial hat den Operationscode 0xb7 und besitzt zwei folgende Operandenbytes, aus denen sich ein Index im Konstantenpool der Klassendatei ergibt. Durch diesen Index wird eine Konstante vom Typ Methodref adressiert, die aus insgesmat fünf Bytes besteht. Die Auswertung dieser Bytes führt zu Verweisen auf weitere Konstanten im Pool. Werden diese Verweise ausgewertet, führt dies zur Zeichenkette java/lang/Object/<init>()V. Der Operationscode 0xb7 in der Klassendatei führt daher direkt zur Mnemonik invokespecial und die beiden Operandenbytes werden auf die genannte Zeichenkette abgebildet.

 

Beispiel: Instanzmethode und main-Methode mit leeren Methodenkörpern

Als zweites Beispiel soll eine Klasse in Jasmin-Assemblersprache erstellt werden, die eine Instantzmethode und eine statische main-Methode enthält. Diese Klasse würde durch den folgenden Java-Quelltext festgelegt:

/* BasicClass.java */

public class BasicClass {
  
  public void method1() {
  }
  
  public static void main(String[] args) {
  }
}

Das folgende Listing BasicClass.j enthält neben den für dieses Beispiel obligatorischen Default-Konstruktor zwei weitere Methodendefinitionen für die gewünschten Methoden. Diese Definitionen bestehen jeweils aus minimalen Methodenkörpern, die lediglich jeweils die Direktiven .limit stack und .limit locals enthalten und mit der JVM-Instruktion return abgeschlossen werden. Zwischen den genannten Direktiven und return können die Methoden entsprechend ausgestaltet werden.

Listing 5.3. BasicClass.j. Der Jasmin-Quelltext enthält eine Instanzmethode method1 und eine main-Methode, jeweils mit minimalem Methodenkörper.

; BasicClass.j

.bytecode 50.0
.class public BasicClass
.super java/lang/Object

.method public <init>()V
  .limit stack 1
  .limit locals 1
  aload_0
  invokespecial java/lang/Object/<init>()V
  return
.end method

.method public method1()V
  .limit stack 0
  .limit locals 1
  ; JVM-Instruktionen
  ; (stack- und locals-Begrenzung anpassen)
  return
.end method

.method public static main([Ljava/lang/String;)V
  .limit stack 0
  .limit locals 1
  ; JVM-Instruktionen
  ; (stack- und locals-Begrenzung anpassen)
  return
.end method

Die beiden Methoden method1 und main nehmen keinen Operandenstapel in Anspruch, was zu .limit stack 0 führt. Das einzige Element im Array der lokalen Variablen der Instanzmethode method1 ist eine Referenz auf ein Objekt der Klasse BasicClass, von dem die Instanzmethode aufgerufen wird (this-Zeiger); das führt zu der Angabe .limit locals 1. Auch die Definition der main-Methode enthält nur ein Element im Array der lokalen Variablen. Dieses Element kommt aber daher zustande, weil die statische Methode einen Parameter (String-Array) besitzt. Das an die Methode übergebene Argument wird als Referenz bei Index 0 im entsprechenden Array gespeichert (lokale Variable 0).

 

Die Methodendirektive .catch

Innerhalb eines Java-Quelltextes werden Ausnahmen (Exceptions) mit der bekannten try-catch-Anweisung behandelt. Die Assemblersprache Jasmin stellt hierfür die Methodendirektive .catch zur Verfügung.

.catch <classname> from <label> to <label> using <label>
.catch <classname> from <offset> to <offset> using <offset>

Direkt nach der Schlüsselbezeichnung .catch folgt der Name einer Fehlerklasse. Zulässige Fehlerklassen sind die Klasse Throwable und deren Unterklassen. Throwable ist die Superklasse aller Fehler und Ausnahmen der Java-Programmiersprache. Die Klasse InterruptedException aus dem Paket java.lang ist z.B. eine Unterklasse dieser Superklasse. Das nachfolgende Listing verwendet diese Unterklasse.

/* CatchExample.java */

public class CatchExample {
  public static void main(String[] args) {
    try {
      Thread.sleep(5000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

Die .catch-Direktive verlangt drei weitere Angaben in Form von Labels oder absoluten Offsets (Index im code-Array). Dier ersten beiden Angaben legen den Programmabschnitt fest, in dem die Ausnahme (Fehler) auftreten kann. Die letzte der drei Angaben gibt den Beginn des Programmabschnitts an, der abgearbeitet werden soll wenn die Ausnahme (Fehler) tatsächlich auftritt (exception handler). Eine Umsetzung des Quelltextes CatchExample.java in Assemblersprache könnte wie folgt lauten:

Listing 5.4. CatchExample.j. Mit der Methodendirektive .catch wird die Behandlung von Ausnahmen realisiert.

; CatchExample.j

.bytecode 50.0
.class public CatchExample
.super java/lang/Object

.method public <init>()V
  .limit stack 1
  .limit locals 1
  aload_0
  invokespecial java/lang/Object/<init>()V
  return
.end method

.method public static main([Ljava/lang/String;)V
  .limit stack 2
  .limit locals 2
Label1:
  ldc2_w 5000
  invokestatic java/lang/Thread/sleep(J)V
Label2:
  goto Label4
Label3:
  astore_1
  aload_1
  invokevirtual java/lang/InterruptedException/printStackTrace()V
Label4:
  return
  .catch java/lang/InterruptedException from Label1 to Label2 using Label3
.end method

Im Jasmin-Listing wurden für die .catch-Direktive drei konventionelle Labels verwendet. Es können auch pc-Labels verwendet werden. Die foglenden Ausschnitte der main-Methode zeigen die Verwendung von pc-Labels und von absoluten Offsets.

Verwendung von pc-Labels:

    0: ldc2_w 5000
    3: invokestatic java/lang/Thread/sleep(J)V
    6: goto 14
    9: astore_1
    10: aload_1
    11: invokevirtual java/lang/InterruptedException/printStackTrace()V
    14: return
    .catch java/lang/InterruptedException from 0 to 6 using 9
  
Direktive .catch nutzt absolute Offsets:

    ldc2_w 5000
    invokestatic java/lang/Thread/sleep(J)V
    goto Label4
    astore_1
    aload_1
    invokevirtual java/lang/InterruptedException/printStackTrace()V
  Label4:
    return
    .catch java/lang/InterruptedException from 0 to 6 using 9

 

Die Methodendirektive .throws

Ein möglicherweise auftretende Exception innerhalb einer Methode muss entweder innerhalb eines Java-Quelltextes mit einer try-catch-Anweisung behandelt oder weitergegeben werden (catch-or-throw-Regel). Das folgende Listing ThrowsExample.java enthält die Methode sleep, die eine InterruptedException auslösen kann. Die möglicherweise auftretende Ausnahme wird mit Hilfe der throws-Klausel weitergegeben (im Beispiel ist keine Behandlung der Ausnahme vorhanden und das Programm würde mit einer Fehlermeldung abbrechen, falls die Exception ausgelöst wird).

/* ThrowsExample.java */

public class ThrowsExample {

  public static void main(String[] args) throws InterruptedException {

    Thread.sleep(5000);
  }
}

Die Weitergabe einer Exception kann im Jasmin-Quelltext mit der Methodendirektive .throws erfolgen.

.throws <classname>

Die Direktive verlangt die Angabe der Fehlerklasse. Zulässige Fehlerklassen sind wie bei der Direktive .catch die Klasse Throwable selbst und deren Unterklassen. Das folgende Listing ist eine Jasmin-Realisierung von ThrowsExample.java.

Listing 5.5. ThrowsExample.j

; ThrowsExample.j

.bytecode 50.0
.class public ThrowsExample
.super java/lang/Object

.method public <init>()V
  .limit stack 1
  .limit locals 1
  aload_0
  invokespecial java/lang/Object/<init>()V
  return
.end method

.method public static main([Ljava/lang/String;)V
  .limit stack 2
  .limit locals 1
  ldc2_w 5000
  invokestatic java/lang/Thread/sleep(J)V
  return
  .throws java/lang/InterruptedException
.end method

 

Die Methodendirektive .line

Mit Hilfe der Methodendirektive .line kann der Assembler dazu veranlasst, Debugging-Informationen in die zu erzeugende Klassendatei aufzunehmen.

.line <integer>

Nach dem Schlüsselwort .line kann ein positive Ganzzahl angegeben angegeben werden, die einer Zeilennummer im Quelltext entspricht. Eine JVM-Instruktion steht im Quelltext in einer bestimmten Zeile. Die Instruktion hat auch eine bestimmte Position (Index) im code-Array. Im folgenden Jasmin-Listing steht z.B. in Zeile 23 die Direktive .line 24 und die darauffolgenden Zeile lautet 3: iload_1. Der pc-Label 3 stimmt mit dem Index des Operationcodes von iload_1 im code-Array überein. Die Wert 24 (Zeilennumer) und 3 (Index im code-Array) werden als Wertepaar explizit in die Klassendatei als Debugging-Informationen aufgenommen. Die beiden Zahlenwerte sind dabei in einem Byteabschnitt enthalten, der als LineNumberTable-Attribut bezeichnet wird.

Listing 5.6. LineExample.j. Die Methodendirektive .line erzwingt die Aufnahme von Debugging-Informationen in die zu erzeugende Klassendatei.

  1  ; LineExample.j
  2  
  3  .bytecode 50.0
  4  .source LineExample.j
  5  .class public LineExample
  6  .super java/lang/Object
  7  
  8  .method public <init>()V
  9    .limit stack 1
 10    .limit locals 1
 11  .line 12
 12    0: aload_0
 13    1: invokespecial java/lang/Object/<init>()V
 14    4: return
 15  .end method
 16  
 17  .method public static main([Ljava/lang/String;)V
 18    .limit stack 2
 19    .limit locals 2
 20  .line 21
 21    0: bipush 10
 22    2: istore_1
 23  .line 24
 24    3: iload_1
 25    4: iconst_1
 26    5: iadd
 27    6: istore_1
 28  .line 29
 29    7: return
 30  .end method

Im folgenden soll kurz darauf eingegangen werden, an welcher Stelle der Klassendatei die Debugging-Informationen stehen. Der Assemblerquelltext enthält vier .line-Direktiven. Demzufolge müssten auch insgesamt vier entsprechende Wertepaare innerhalb der Klassendatei vorhanden sein.

Abbildung 5.1. Hexadezimale Darstellung der Klassendatei LineExample.class, die aus Listing 5.6 hervorgegangen ist.

ca fe ba be 00 00 00 32 00 0f 0a 00 0e 00 07 01     .......2........     0
00 0d 4c 69 6e 65 45 78 61 6d 70 6c 65 2e 6a 01     ..LineExample.j.     16
00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65     ..java/lang/Obje     32
63 74 01 00 0a 53 6f 75 72 63 65 46 69 6c 65 01     ct...SourceFile.     48
00 06 3c 69 6e 69 74 3e 01 00 04 6d 61 69 6e 0c     ..<init>...main.     64
00 05 00 0d 01 00 04 43 6f 64 65 01 00 0b 4c 69     .......Code...Li     80
6e 65 45 78 61 6d 70 6c 65 01 00 0f 4c 69 6e 65     neExample...Line     96
4e 75 6d 62 65 72 54 61 62 6c 65 07 00 09 01 00     NumberTable.....     112
16 28 5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74     .([Ljava/lang/St     128
72 69 6e 67 3b 29 56 01 00 03 28 29 56 07 00 03     ring;)V...()V...     144
00 21 00 0b 00 0e 00 00 00 00 00 02 00 01 00 05     .!..............     160
00 0d 00 01 00 08 00 00 00 1d 00 01 00 01 00 00     ................     176
00 05 2a b7 00 01 b1 00 00 00 01 00 0a 00 00 00     ..*.............     192
06 00 01 00 00 00 0c 00 09 00 06 00 0c 00 01 00     ................     208
08 00 00 00 28 00 02 00 02 00 00 00 08 10 0a 3c     ....(..........<     224
1b 04 60 3c b1 00 00 00 01 00 0a 00 00 00 0e 00     ..`<............     240
03 00 00 00 15 00 03 00 18 00 07 00 1d 00 01 00     ................     256
04 00 00 00 02 00 02                                .......              272

Die folgende Übersicht macht deutlich welche .line-Direktive für die Aufnahme bestimmter Bytes in die Klassendatei verantwortlich ist und welche Bedeutung diese Bytes haben (die Angabe Index ist dabei als Index im code-Array zu verstehen).

.line 12 --> Bytes 211 - 214 
             00 00 00 0c     (Index: 0x0000, Zeile: 0x000c) 

.line 21 --> Bytes 257 - 260
             00 00 00 15     (Index: 0x0000, Zeile: 0x0015)
.line 24 --> Bytes 261 - 264
             00 03 00 18     (Index: 0x0003, Zeile: 0x0018)
.line 29 --> Bytes 265 - 268
             00 07 00 1d     (Index: 0x0007, Zeile: 0x001d)

Diese Bytes können nun zusammen mit dem Namen der Quelldatei (.source-Direktive) von einem Debugger zur Fehlersuche entsprechend ausgewertet werden.

 

Die Methodendirektive .var

Ebenso wie die Methodendirektive .line führt die Verwendung von .var dazu, dass durch den Assembler Debugging-Informationen in die zu erzeugende Klassendatei geschrieben werden. Die Informationen im Falle von .var beziehen sich auf die lokalen Variablen einer Methode: Name, Descriptor, Signatur, Index im Array der lokalen Variablen. Des Weiteren wird für die lokalen Variablen auch ein Bereich angegeben, der die Sichtbarkeit der Variablen regelt. Ein Debugger kann im Falle von Fehlern bei der Programmausführung auf diese Informationen zurückgreifen. Für die Angaben zur Sichtbarkeit der lokalen Variablen können sowohl Labels als auch absolute Offsets genutzt werden (der Assembler Jasmin verlangt im Quelltext, dass die Methodendirektive in einer einzigen Zeile notiert ist). Eine Angabe zur Signatur einer lokalen Variablen wird nur bei Bedarf benötigt; der Signaturbereich in der Beschreibung der .var-Direktive ist deshalb in eckigen Klammern notiert:

.var <varnumber> is <varname> <fielddescriptor>
    [signature <fieldsignature>] from <label> to <label>

.var <varnumber> is <varname> <fielddescriptor>
    [signature <fieldsignature>] from <offset> to <offset>

Als Ausgangspunkt für ein Beispiellisting zur .var-Direktive soll der Java-Quelltext VarExample.java dienen.

/* VarExample.java */

import java.util.*;

public class VarExample {
  public static void main(String[] args) {
    int i = 3;
    int j = 4;
    Vector<Integer> v = new Vector<Integer>();
    v.add(new Integer(i));
  }
}

Innerhalb der main-Methode werden die drei lokalen Variablen i, j und v deklariert. Eine Umsetzung der Methode in Assemblersprache, mit Debugging-Informationen zu den lokalen Variablen, könnte dabei das folgende Fragment enthalten:

.limit locals 4
.var 0 is args [Ljava/lang/String; from ... to ...
.var 1 is i I from ... to ...
.var 2 is j I from ... to ...
.var 3 is v Ljava/util/Vector; 
    signature "Ljava/util/Vector<Ljava/lang/Integer;>;" from ... to ...

Neben den drei lokalen Variablen i, j und v, für die die Indizes 1, 2 und 3 im Array der lokalen Variablen gelten, ist der Anfang des Arrays für den einzigen Parameter args der main-Methode reserviert. Nach den Variablennamen stehen die Felddeskriptoren der lokalen Variablen. Der Deskriptor der Variablen v des Typs Vector ist Ljava/util/Vector;. Die Signatur von v ist ein Zeichenkette, die den generischen Typ Vector<E> der lokalen Variablen repräsentiert. VarExample.j enthält die vollständige Umsetztung des Java-Quelltextes VarExample.java in Assemblersprache.

Listing 5.7. VarExample.j. Beispiel zur Methodendirektive .var.

; VarExample.j

.bytecode 50.0
.class public VarExample
.super java/lang/Object

.method public <init>()V
  .limit stack 1
  .limit locals 1
  .var 0 is this LVarExample; from Label0 to Label4
Label0:
  aload_0
  invokespecial java/lang/Object/<init>()V
Label4:
  4: return
.end method

.method public static main([Ljava/lang/String;)V
  .limit stack 4
  .limit locals 4
  .var 0 is args [Ljava/lang/String; from Label0 to Label25
  .var 1 is i I from Label2 to Label25
  .var 2 is j I from Label4 to Label25
  ; folgende .var-Direktive in einer einzigen Zeile anordnen
  .var 3 is v Ljava/util/Vector; 
    signature "Ljava/util/Vector<Ljava/lang/Integer;>;" from Label12 to Label25
Label0:
  iconst_3
  istore_1
Label2:
  iconst_4
  istore_2
Label4:
  new java/util/Vector
  dup
  invokespecial java/util/Vector/<init>()V
  astore_3
Label12:
  aload_3
  new java/lang/Integer
  dup
  iload_1
  invokespecial java/lang/Integer/<init>(I)V
  invokevirtual java/util/Vector/add(Ljava/lang/Object;)Z
  pop
Label25:
  return
.end method

Anmerkung: Soll die Klassendatei VarExample.class aus VarExample.java mit Hilfe des Compilers javac erzeugt werden, müsste die Option -g oder -g:vars verwendet werden, damit der Java-Compiler Debugging-Informationen zu den lokalen Variablen in die Klassendatei schreibt:

> javac -g VarExample.java

 

 

 

Diese Seite nutzt Google-Dienste - siehe dazu Datenschutz.

Copyright © 2006, 2007 Harald Roeder