자바의 무한 루프


82

whileJava에서 다음 무한 루프를 살펴보십시오 . 아래 명령문에 대해 컴파일 타임 오류가 발생합니다.

while(true) {
    System.out.println("inside while");
}

System.out.println("while terminated"); //Unreachable statement - compiler-error.

그러나 다음과 같은 무한 while루프는 잘 작동하며 방금 조건을 부울 변수로 바꾼 오류가 발생하지 않습니다.

boolean b=true;

while(b) {
    System.out.println("inside while");
}

System.out.println("while terminated"); //No error here.

두 번째 경우에도 부울 변수 b가 true 이므로 컴파일러가 전혀 불평하지 않기 때문에 루프 뒤의 명령문에 분명히 도달 할 수 없습니다 . 왜?


편집 : 다음 버전은 while명백한 것처럼 무한 루프에 갇히지 만 if루프 내의 조건이 항상 false이며 결과적으로 루프가 반환되지 않고 컴파일러에 의해 결정될 수 있음에도 불구하고 그 아래의 명령문에 대해 컴파일러 오류가 발생 하지 않습니다. 컴파일 타임 자체.

while(true) {

    if(false) {
        break;
    }

    System.out.println("inside while");
}

System.out.println("while terminated"); //No error here.

while(true) {

    if(false)  { //if true then also
        return;  //Replacing return with break fixes the following error.
    }

    System.out.println("inside while");
}

System.out.println("while terminated"); //Compiler-error - unreachable statement.

while(true) {

    if(true) {
        System.out.println("inside if");
        return;
    }

    System.out.println("inside while"); //No error here.
}

System.out.println("while terminated"); //Compiler-error - unreachable statement.

편집 : 와 같은 것 if하고 while.

if(false) {
    System.out.println("inside if"); //No error here.
}

while(false) {
    System.out.println("inside while");
    // Compiler's complain - unreachable statement.
}

while(true) {

    if(true) {
        System.out.println("inside if");
        break;
    }

    System.out.println("inside while"); //No error here.
}      

다음 버전 while도 무한 루프에 빠집니다.

while(true) {

    try {
        System.out.println("inside while");
        return;   //Replacing return with break makes no difference here.
    } finally {
        continue;
    }
}

이는 블록 자체 내에서 명령문이 먼저 만나 finally더라도 블록이 항상 실행 return되기 때문 try입니다.


46
무슨 상관이야? 분명히 컴파일러의 기능 일뿐입니다. 이런 종류의 것들에 대해 걱정하지 마십시오.
CJ7 2011

17
다른 스레드가 로컬 비 정적 변수를 어떻게 변경할 수 있습니까?
CJ7 2011

4
객체의 내부 상태는 반사를 통해 동시에 변경 될 수 있습니다. 이것이 JLS가 최종 (상수) 표현식 만 검사하도록 요구하는 이유입니다.
lsoliveira 2011

6
나는이 어리석은 오류를 싫어한다. 연결할 수없는 코드는 오류가 아닌 경고 여야합니다.
user606723 dec.

2
@ CJ7 : 나는 이것을 "기능"이라고 부르지 않을 것이며, 준수하는 Java 컴파일러를 구현하는 것은 (이유없이) 매우 지루하게 만듭니다. 설계에 따른 공급 업체 종속을 즐기십시오.
L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳

답변:


105

컴파일러는 첫 번째 표현식이 항상 무한 루프를 생성 한다는 것을 쉽고 분명하게 증명할 수 있지만 두 번째 표현식 은 쉽지 않습니다. 장난감 예제에서는 간단하지만 다음과 같은 경우에는 어떻게됩니까?

  • 변수의 내용을 파일에서 읽었습니까?
  • 변수가 로컬이 아니고 다른 스레드에서 수정할 수 있습니까?
  • 변수가 일부 사용자 입력에 의존합니까?

컴파일러는 그 길을 완전히 포기하기 때문에 더 간단한 경우를 확인하지 않습니다. 왜? 사양에서 금지하는 것이 훨씬 더 어렵 기 때문 입니다. 섹션 14.21 참조 :

(그런데 내 컴파일러 변수가 선언되면 불평합니다 final.)


26
-1-컴파일러가 할 수있는 일이 아닙니다. 수표가 더 쉬워 지거나 더 어려워지는 것은 아닙니다. Java 언어 사양에 따라 컴파일러가 수행 할 수있는 작업 에 대한 것 입니다. 코드의 두 번째 버전은 JLS에 따라 유효한 Java 이므로 컴파일 오류가 잘못되었습니다.
Stephen C

10
@StephenC-그 정보에 감사드립니다. 많은 것을 반영하도록 행복하게 업데이트되었습니다.
Wayne

여전히 -1; 여기에 "what ifs"가 적용되지 않습니다. 그대로, 컴파일러 실제로 문제를 해결할 수 있습니다 . 정적 분석 용어에서는이를 상수 전파라고하며 다른 많은 상황에서 성공적으로 광범위하게 사용됩니다. JLS가 유일한 이유이며 문제를 해결하는 것이 얼마나 어려운지는 중요하지 않습니다. 물론 JLS가 처음에 그렇게 작성된 이유는 그 문제의 어려움과 관련이있을 수 있지만, 개인적으로는 실제로 다른 컴파일러에 걸쳐 표준화 된 상수 전파 솔루션을 적용하기가 어렵 기 때문이라고 생각합니다 .
Oak :

2
@Oak-저는 "[OP의] 장난감 예제에서는 간단합니다"라는 대답에 동의 했으며 Stephen의 의견에 따라 JLS가 제한 요소라는 점을 인정했습니다. 나는 우리가 동의한다고 확신합니다.
Wayne

55

사양 에 따르면 while 문에 대해 다음과 같이 말합니다.

while 문은 다음 중 하나 이상이 참인 경우 정상적으로 완료 될 수 있습니다.

  • while 문에 도달 할 수 있고 조건식이 true 값을 갖는 상수식이 아닙니다.
  • while 문을 종료하는 도달 가능한 break 문이 있습니다. \

따라서 컴파일러는 while 조건이 참 값을 갖는 상수이거나 while 내에 break 문이있는 경우에만 while 문 다음에 오는 코드에 도달 할 수 없다고 말합니다. 두 번째 경우 b의 값이 상수가 아니기 때문에 그 뒤에 오는 코드에 도달 할 수 없다고 간주하지 않습니다. 해당 링크 뒤에는 도달 할 수없는 것으로 간주되는 항목과 그렇지 않은 항목에 대한 자세한 정보를 제공하는 훨씬 더 많은 정보가 있습니다.


3
+1은 컴파일러 작성자가 생각할 수있는 것이 아니라 생각할 수없는 것이 아니라는 점에 주목합니다.
yshavit 2011

14

true는 상수이고 b는 루프에서 변경 될 수 있기 때문입니다.


1
b가 루프에서 변경 되지 않으므로 정확하지만 관련이 없습니다 . true를 사용하는 루프 에 break 문 포함될 수 있다고 똑같이 주장 할 수 있습니다.
deworde

@deworde-변경 될 가능성이있는 한 (다른 스레드에 의해) 컴파일러는 오류를 호출 할 수 없습니다. 루프 자체를 보는 것만으로는 루프가 실행되는 동안 b가 변경 될 것인지 여부를 알 수 없습니다.
Peter Recore

10

변수 상태를 분석하는 것은 어렵 기 때문에 컴파일러는 거의 포기하고 원하는 것을 할 수 있습니다. 또한 Java 언어 사양에는 컴파일러가 도달 할 수없는 코드를 감지하는 방법에 대한 명확한 규칙 이 있습니다 .

컴파일러를 속이는 방법에는 여러 가지가 있습니다. 또 다른 일반적인 예는

public void test()
{
    return;
    System.out.println("Hello");
}

컴파일러는 해당 영역이 반응 할 수 없다는 것을 인식하므로 작동하지 않습니다. 대신 할 수 있습니다.

public void test()
{
    if (2 > 1) return;
    System.out.println("Hello");
}

이것은 컴파일러가 표현식이 결코 거짓이 아니라는 것을 인식 할 수 없기 때문에 작동합니다.


6
다른 답변에서 언급했듯이 이것은 컴파일러가 도달 할 수없는 코드를 감지하기가 쉽거나 어려울 수있는 것과 (직접적으로) 관련이 없습니다. JLS는 도달 할 수없는 명령문을 감지하는 규칙 을 지정 하기 위해 많은 시간을 할애 합니다. 이러한 규칙에 따르면 첫 번째 예제는 유효한 Java가 아니며 두 번째 예제는 유효한 Java입니다. 그게 다야. 컴파일러는 지정된대로 규칙을 구현합니다.
Stephen C

나는 JLS 주장을 따르지 않는다. 컴파일러는 모든 합법적 인 Java를 바이트 코드로 변환 할 의무가 있습니까? 무의미한 것으로 입증 될 수 있더라도 말입니다.
emory

@emory 예, 표준을 준수하고 합법적 인 Java 코드를 컴파일하는 표준 호환 브라우저의 정의입니다. 표준을 준수하지 않는 브라우저를 사용하는 경우 몇 가지 멋진 기능이있을 수 있지만 이제 "분명한"규칙이 무엇인지에 대한 바쁜 컴파일러 디자이너의 이론에 의존하게됩니다. 정말 끔찍한 시나리오입니다. "왜 누군가 가 같은 조건에서 두 개의 조건문 을 사용하고 싶을 까요? 나는 결코 가지고 있지 않습니다")
deworde

사용하는이 예제 는 컴파일러가 불평하지 않고 시퀀스도 컴파일하기 때문에와 if약간 다릅니다 . IIRC, 이것은 JLS의 특별한 예외입니다. 컴파일러는 다음과 같이 쉽게 도달 할 수없는 코드를 감지 할 수 있습니다 . whileif(true) return; System.out.println("Hello");ifwhile
Christian Semrau 2011

2
@emory-써드 파티 어노테이션 프로세서를 통해 Java 프로그램을 공급할 때 ... 더 이상 Java가 아닙니다. 오히려 주석 프로세서가 구현하는 모든 언어 확장 및 수정으로 오버레이 된 Java 입니다. 그러나 그것은 여기서 일어나고있는 것이 아닙니다. OP의 질문은 바닐라 자바에 관한 것이며, JLS 규칙은 유효한 바닐라 자바 ​​프로그램이 무엇인지, 무엇이 아닌지 결정합니다.
Stephen C

6

후자는 도달 할 수 없습니다. 부울 b는 루프 내부 어딘가에서 종료 조건을 유발하는 거짓으로 변경 될 가능성이 있습니다.


b가 루프에서 변경되지 않으므로 정확하지만 관련이 없습니다. true를 사용하는 루프에 종료 조건을 제공하는 break 문이 포함될 수 있다고 똑같이 주장 할 수 있습니다.
deworde

4

내 생각에 변수 "b"는 그 값을 변경할 가능성이 있으므로 컴파일러 System.out.println("while terminated"); 가 도달 할 수 있다고 생각 합니다.


4

컴파일러는 완벽하지 않습니다.

컴파일러의 책임은 실행을 확인하는 것이 아니라 구문을 확인하는 것입니다. 컴파일러는 강력한 형식의 언어에서 많은 런타임 문제를 궁극적으로 포착하고 방지 할 수 있지만 이러한 오류를 모두 포착 할 수는 없습니다.

실용적인 솔루션은 컴파일러 검사를 보완하기 위해 단위 테스트 배터리를 사용하거나 기본 변수 및 중지 조건에 의존하는 대신 강력한 것으로 알려진 논리를 구현하기 위해 객체 지향 구성 요소를 사용하는 것입니다.

강력한 타이핑 및 OO : 컴파일러의 효율성 향상

일부 오류는 본질적으로 구문 적이며 Java에서는 강력한 유형 지정으로 인해 많은 런타임 예외를 포착 할 수 있습니다. 그러나 더 나은 유형을 사용하면 컴파일러가 더 나은 논리를 적용하도록 도울 수 있습니다.

컴파일러가 로직을보다 효과적으로 적용하기를 원하는 경우 Java에서 솔루션은 이러한 로직을 적용 할 수있는 강력하고 필수 객체를 빌드하고 이러한 객체를 사용하여 기본 요소가 아닌 애플리케이션을 빌드하는 것입니다.

이에 대한 전형적인 예는 반복자 패턴을 사용하는 것입니다. Java의 foreach 루프와 결합 된이 구조는 단순한 while 루프보다 설명하는 버그 유형에 덜 취약합니다.


OO는 정적 강력한 타이핑 메커니즘이 오류를 찾는 데 도움이되는 유일한 방법이 아닙니다. Haskell의 매개 변수 유형과 유형 클래스 (OO 언어에서 클래스라고 부르는 것과 약간 다릅니다)는 실제로이 점에서 훨씬 더 좋습니다.
leftaroundabout

3

컴파일러는 b포함 할 수있는 값을 실행할만큼 정교하지 않습니다 (한 번만 할당하더라도). 첫 번째 예제는 조건이 가변적이지 않기 때문에 컴파일러가 무한 루프가 될 것임을 쉽게 알 수 있습니다.


4
이것은 컴파일러의 "정교함"과 관련이 없습니다. JLS는 도달 할 수없는 명령문을 감지하는 규칙을 지정하기 위해 많은 시간을 할애합니다. 이러한 규칙에 따르면 첫 번째 예제는 유효한 Java가 아니며 두 번째 예제는 유효한 Java입니다. 그게 다야. 컴파일러 작성자는 지정된대로 규칙을 구현해야합니다. 그렇지 않으면 그의 컴파일러가 부적합합니다.
Stephen C

3

컴파일러가 첫 번째 경우 컴파일을 거부 한 것에 놀랐습니다. 나는 이상하게 보인다.

그러나 두 번째 경우는 첫 번째 경우에 최적화되지 않았습니다. (a) 다른 스레드가 b(b) 호출 된 함수의 값을 b부작용으로 수정할 수 있기 때문입니다.


2
놀란다면 Java 언어 사양을 충분히 읽지 않은 것입니다. :-)
Stephen C

1
하하 :) 내가 서있는 곳을 아는 것이 좋습니다. 감사!
sarnold 2011

3

사실 나는 아무도 그것을 QUITE 옳다고 생각하지 않는다 (적어도 원래 질문자의 의미에서). OQ는 다음을 계속 언급합니다.

정확하지만 관련이 없습니다. b가 루프에서 변경되지 않기 때문입니다.

그러나 마지막 라인에 도달 할 수 있기 때문에 중요하지 않습니다. 해당 코드를 가져 와서 클래스 파일로 컴파일하고 클래스 파일을 다른 사람 (예 : 라이브러리)에게 건네면 컴파일 된 클래스를 리플렉션을 통해 "b"를 수정하는 코드와 연결하여 루프를 종료하고 마지막 실행할 줄.

이는 상수가 아닌 변수 (또는 사용 된 위치에서 상수로 컴파일되는 final 변수에 해당합니다. 종종 클래스를 참조하는 클래스가 아닌 최종 클래스로 다시 컴파일하면 기괴한 오류가 발생하기도합니다. 클래스는 오류없이 이전 값을 유지합니다.)

저는 리플렉션 기능을 사용하여 다른 클래스의 최종 비공개 변수를 수정하여 구입 한 라이브러리의 클래스에 원숭이 패치를 적용했습니다. 공급 업체의 공식 패치를 기다리는 동안 개발을 계속할 수 있도록 버그를 수정했습니다.

그건 그렇고, 요즘은 실제로 작동하지 않을 수 있습니다. 전에 해봤지만 그런 작은 루프가 CPU 캐시에 캐시 될 가능성이 있고 변수가 휘발성으로 표시되지 않았기 때문에 캐시 된 코드가 절대로 작동하지 않을 수 있습니다. 새로운 가치를 선택하십시오. 나는 이것을 실제로 본 적이 없지만 이론적으로는 사실이라고 믿습니다.


좋은 지적. 나는 그것이 b메소드 변수 라고 가정 합니다. 리플렉션을 사용하여 메서드 변수를 실제로 수정할 수 있습니까? 전체적인 요점에 관계없이 마지막 라인에 도달 할 수 없다고 가정해서는 안됩니다.
모리

아, 당신 말이 맞아요. b가 멤버라고 생각 했어요. b가 메서드 변수이면 컴파일러가 "Can"이 변경되지 않는다는 것을 알지만 변경하지 않는다고 믿습니다 (결국 다른 모든 답변을 QUITE로 수정)
Bill K

3

컴파일러가 가능하지만 너무 많은 베이비 시팅 작업을하지 않기 때문입니다.

표시된 예제는 컴파일러가 무한 루프를 감지하는 데 간단하고 합리적입니다. 하지만 변수와의 관계없이 1000 줄의 코드를 삽입하는 것은 b어떨까요? 그리고 그 진술은 모두 b = true;어떻습니까? 컴파일러는 확실히 결과를 평가하고 결과가 결국 사실이라고 말할 수 있습니다.while 루프 있지만 실제 프로젝트를 컴파일하는 데 얼마나 느릴까요?

추신, 보푸라기 도구는 확실히 당신을 위해 그것을해야합니다.


2

컴파일러의 관점에서 bin while(b)은 어딘가에서 false로 변경 될 수 있습니다. 컴파일러는 검사를 신경 쓰지 않습니다.

재미있는 시도를 들어 while(1 < 2), for(int i = 0; i < 1; i--)


2

식은 런타임에 평가되므로 스칼라 값 "true"를 부울 변수와 같은 것으로 바꿀 때 스칼라 값을 부울 식으로 변경 했으므로 컴파일러는 컴파일 타임에이를 알 수있는 방법이 없습니다.


2

컴파일러가 부울이 true런타임에 평가 될 것이라는 결론을 내릴 수있는 경우 해당 오류가 발생합니다. 컴파일러는 선언 한 변수 가 변경 될 (우리는 인간으로서 알 수는 있지만).

이 사실을 강조하기 위해 변수가 finalJava에서 선언 된 경우 대부분의 컴파일러는 값을 대체 한 것과 동일한 오류를 발생시킵니다. 이는 변수가 컴파일 타임에 정의되고 (런타임에 변경 될 수 없음) 컴파일러가식이 true런타임에 평가되는지 결정적으로 결정할 수 있기 때문입니다.


2

첫 번째 Statement는 while 루프의 조건에서 상수를 지정했기 때문에 항상 무한 루프가 발생합니다. 두 번째 경우 컴파일러에서 루프 내부에서 b 값의 변경 가능성이 있다고 가정합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.