4.20. JVM-Befehlssatz: Kurzbeschreibung T
Der JVM-Befehl tableswitch
realisiert eine
switch
-Anweisung auf Bytecodeebene.
Im Anschluss an die Mnemonik tableswitch
bzw. den Operationscode
aa
folgen eine Reihe von Operandenbytes, die für die Codierung der
switch
-Anweisung notwendig sind. Dabei ist die Anzahl der Operandenbytes
von der Anzahl der verwendeten case
-Labels der Mehrfachverzweigung
abhängig.
Eine switch
-Anweisung kann wie folgt aufgebaut sein:
switch (index) {
case konstante:
anweisung;
...
default:
...
}
Eine Übersetzung einer switch
-Anweisung in Bytecode kann auch
den JVM-Befehl
lookupswitch
enthalten. Im unteren Beispiel wurden
die drei case
-Konstanten 3, 4 und 6 verwendet und der erzeugte Bytecode
enthält eine tableswitch
-Realisierung. Die folgende Zusammenstellung
zeigt den Übergang von einer tableswitch
-Realisierung einer
switch
-Anweisung zu einer Realisierung mit dem Befehl
lookupswitch
unter Verwendung unterschiedlicher
case
-Konstanten (verwendeter Compiler: javac
,
Version 1.6.0):
Der interne Aufbau des Befehls tableswitch
kann eine
Konstantenfolge wie z.B. 1, 2, 3 effizienter umsetzen und wird deshalb in diesem
Fall verwendet. Liegen die Werte der case
-Konstanten zu weit
auseinander wird auf eine lookupswitch
-Lösung zurückgegriffen.
Die auf den Opcode von tableswitch
folgenden Operandenbytes
codieren eine Sprungtabelle. Der auf dem Operandenstapel befindliche Wert
index wird mit den case
-Konstanten
verglichen. Stimmt eine dieser Konstanten mit dem Vergleichswert überein
(case
"matches"), dann werden die Anweisungen nach diesem
case
-Label ausgeführt. In der Sprungtabelle werden Offsets zu diesen
case
- bzw. dafault
-Anweisungen gespeichert. Die
Offsetwerte beziehen sich auf die Bytecodestelle des Opcodes von
tableswitch
. Aus jeweils vier vorzeichenlos gewerteten Operandenbytes
können vorzeichenbehaftete 32-Bit-Ganzzahlen berechnet werden:
default = (default1 << 24) | (default2 << 16) |
(default3 << 8) | default4
low = (low1 << 24) | (low2 << 16) |
(low3 << 8) | low4
high = (high1 << 24) | (high2 << 16) |
(high3 << 8) | high4
offset1 = (offset11 << 24) | (offset12 << 16) |
(offset13 << 8) | offset14
...
Falls der auf dem Operandenstapel liegende Wert index
kleiner als low oder größer als high ist,
wird zur Bytecodestelle mit Offset default gesprungen.
Gilt hingegen low <= index <=
high dann wird die case
-Offset-Nummer
index - low + 1 beachtet.
Im unmittelbaren Anschluss an den Opcode aa
können mehrere
sogenannte Füllbytes (padding bytes) mit dem numerischen Wert 0 angeordnet sein
(0 bis 3 dieser Bytes sind möglich). Diese Füllbytes werden eingefügt, damit die
Bytecodestelle des Operandenbytes default1 ein vielfaches
der Zahl 4 ist. Die genannte Stelle im Bytecode bezieht sich dabei auf die
Bytecodestelle 0 einer jeden Methode. Die Operandenbytes unterliegen damit einem
gewünschten 4-Byte-Raster.
Beispiel:
Eine switch
-Anweisung soll auf Bytecodeebene mit Hilfe des
folgenden kurzen Listings näher untersucht werden:
public class Test {
public static void main(String[] args) {
int a = 4;
int b = 0;
switch (a) {
case 3: b++;
break;
case 4: b += 2;
break;
case 6: b += 3;
break;
default: b += 5;
}
b--;
}
}
Das kurze Beispielprogramm kann mittels javac
in die Klassendatei
Test.class
übersetzt werden. Die in dieser erzeugten Datei enthaltenen
Zahlenwerte (Bytecode) für die switch
-Anweisung lauten in hexadezimaler
Schreibweise:
aa 00 00 00 00 00 31 00 00 00 03 00 00 00 06 00
00 00 1f 00 00 00 25 00 00 00 31 00 00 00 2b
Auf den Opcode aa
für die zugehörige Mnemonik tableswitch
folgen im Beispiel 30 Operandenbytes, die der Reihe nach wie folgt zugeordnet werden
können:
Die folgende Übersicht listet die zum betrachteten Programmausschnitt
korrespondierenden JVM-Befehle auf, wie sie mit Hilfe des Klassendatei-Disassemblers
javap
erhalten werden können. Die einzelnen Operandenbyte-Blöcke zu
tableswitch
werden anschließend kurz erläutert.
int a = 4; 0: iconst_4
1: istore_1
int b = 0; 2: iconst_0
3: istore_2
switch (a) { 4: iload_1
5: tableswitch { //3 to 6
3: 36;
4: 42;
5: 54;
6: 48;
default: 54 }
case 3: b++; 36: iinc 2, 1
break; 39: goto 57
case 4: b += 2; 42: iinc 2,2
break; 45: goto 57
case 6: b += 3; 48: iinc 2,3
break; 51: goto 57
default: b += 5; 54: iinc 2,5
}
b--; 57: iinc 2,-1
Operationscode des Befehls tableswitch und dessen Operandenbytes (5 - 35):
5: aa Opcode tableswitch
6: 00 00 2 Padding-Bytes
8: 00 00 00 31 default = 49, (5 + 49 = 54)
12: 00 00 00 03 low = 3
16: 00 00 00 06 high = 6
20: 00 00 00 1f offset1 = 31, (5 + 31 = 36)
24: 00 00 00 25 offset2 = 37, (5 + 37 = 42)
28: 00 00 00 31 offset3 = 49, (5 + 49 = 54)
32: 00 00 00 2b offset4 = 43, (5 + 43 = 48)
Der 3. Offsetwert offset3 hat den gleichen Wert wie
default und steht "virtuell" für die im Quelltext nicht
vorhandene case
-Konstante 5. Bei Konstanten wie z.B. 3, 4 und 10 müssten
unter Verwendung von tableswitch
zu viele "virtuelle" Offsets eingefügt
werden und eine derartige switch
-Realisierung wäre deshalb nicht
effizient. Es wird daher
lookupswitch
unter Verwendung von Match-Offset-Paaren verwendet.