나는 당신 goto
이 Java 언어로 예약 된 키워드이지만 실제로 사용되지 않는다는 것을 대부분 알고 있습니다. 또한 goto
JVM (Java Virtual Machine) opcode 임을 알고있을 것입니다 . 나는 JVM 수준에서의 조합을 사용하여 구현하는 모든에게 자바, 스칼라와 코 틀린의 정교한 제어 흐름 구조를 이해 할수 goto
과 ifeq
, ifle
, iflt
, 등
JVM을 사양을 보면 https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.goto_w 나는 거기에 또한 참조 goto_w
연산 코드가. goto
2 바이트 분기 오프셋을 사용하는 반면 goto_w
4 바이트 분기 오프셋을 사용합니다. 사양은
있지만 goto_w 명령 오프셋 4 바이트 지점을 얻어, 다른 요인은 65,535 바이트 (§4.11)에있어서의 크기를 제한한다. 이 한계는 다음 릴리스의 Java Virtual Machine에서 발생할 수 있습니다.
goto_w
다른 *_w
opcode 와 마찬가지로 미래를 보장하는 것처럼 들립니다 . 그러나 필요에 따라 조정 goto_w
하여 2 개의 더 중요한 바이트가 0으로 설정되고 2 개의 덜 중요한 바이트가와 동일하게 사용될 수 있다는 것도 나에게 발생 goto
합니다.
예를 들어, 다음 Java 스위치 케이스 (또는 스칼라 매치 케이스)가 주어진 경우 :
12: lookupswitch {
112785: 48 // case "red"
3027034: 76 // case "green"
98619139: 62 // case "blue"
default: 87
}
48: aload_2
49: ldc #17 // String red
51: invokevirtual #18
// Method java/lang/String.equals:(Ljava/lang/Object;)Z
54: ifeq 87
57: iconst_0
58: istore_3
59: goto 87
62: aload_2
63: ldc #19 // String green
65: invokevirtual #18
// Method java/lang/String.equals:(Ljava/lang/Object;)Z
68: ifeq 87
71: iconst_1
72: istore_3
73: goto 87
76: aload_2
77: ldc #20 // String blue
79: invokevirtual #18
// etc.
우리는 그것을 다음과 같이 다시 쓸 수 있습니다
12: lookupswitch {
112785: 48
3027034: 78
98619139: 64
default: 91
}
48: aload_2
49: ldc #17 // String red
51: invokevirtual #18
// Method java/lang/String.equals:(Ljava/lang/Object;)Z
54: ifeq 91 // 00 5B
57: iconst_0
58: istore_3
59: goto_w 91 // 00 00 00 5B
64: aload_2
65: ldc #19 // String green
67: invokevirtual #18
// Method java/lang/String.equals:(Ljava/lang/Object;)Z
70: ifeq 91
73: iconst_1
74: istore_3
75: goto_w 91
79: aload_2
81: ldc #20 // String blue
83: invokevirtual #18
// etc.
goto_w
s 를 수용하기 위해 “줄 번호”를 변경하는 실수를했기 때문에 실제로 이것을 시도하지 않았습니다 . 그러나 그것은 사양에 있기 때문에 가능할 것입니다.
내 질문은 컴파일러 또는 다른 바이트 코드 생성기가 goto_w
현재 65535 제한으로 사용할 수있는 이유가 있는지 여부입니다 .
답변
메소드 코드의 크기는 64K만큼 클 수 있습니다.
short의 분기 오프셋은 goto
-32768에서 32767 사이의 부호있는 16 비트 정수입니다.
따라서 짧은 오프셋은 65K 방법의 시작에서 끝으로 점프하기에 충분하지 않습니다.
심지어 javac
때로는 방출한다 goto_w
. 예를 들면 다음과 같습니다.
public class WideGoto {
public static void main(String[] args) {
for (int i = 0; i < 1_000_000_000; ) {
i += 123456;
// ... repeat 10K times ...
}
}
}
로 디 컴파일 javap -c
:
public static void main(java.lang.String[]);
Code:
0: iconst_0
1: istore_1
2: iload_1
3: ldc #2
5: if_icmplt 13
8: goto_w 50018 // <<< Here it is! A jump to the end of the loop
...
답변
goto_w
분기가에 맞을 때 사용할 이유가 없습니다 goto
. 그러나 분기가 뒤로 갈 수 있기 때문에 부호있는 오프셋을 사용하여 분기가 상대적 인 것을 놓친 것 같습니다 .
javap
인쇄하기 전에 결과 절대 목표 주소를 계산 하므로과 같은 도구의 출력을 볼 때이를 알 수 없습니다 .
그래서 goto
의의 범위는 -327678 … +32767
항상 각 가능한 대상 위치를 처리하는 것만으로는 충분하지 않습니다 0 … +65535
범위.
예를 들어 다음 방법은 goto_w
시작 부분에 지침 이 있습니다 .
public static void methodWithLargeJump(int i) {
for(; i == 0;) {
try {x();} finally { switch(i){ case 1: try {x();} finally { switch(i){ case 1:
try {x();} finally { switch(i){ case 1: try {x();} finally { switch(i){ case 1:
try {x();} finally { switch(i){ case 1: try {x();} finally { switch(i){ case 1:
try {x();} finally { switch(i){ case 1: try {x();} finally { switch(i){ case 1:
try {x();} finally { switch(i){ case 1: try {x();} finally { switch(i){ case 1:
} } } } } } } } } } } } } } } } } } } }
}
}
static void x() {}
Compiled from "Main.java"
class LargeJump {
public static void methodWithLargeJump(int);
Code:
0: iload_0
1: ifeq 9
4: goto_w 57567
…
답변
일부 컴파일러 (1.6.0 및 11.0.7에서 시도)에서 메소드가 goto_w를 필요로 할 정도로 충분히 크면 goto_w 만을 사용 하는 것으로 보입니다. 로컬 점프가 매우 많더라도 여전히 goto_w를 사용합니다.