컴파일러가 죽은 코드를 감지 할 수 없음을 증명


32

저는 다양한 주제로 겨울 코스를 가르 칠 계획입니다. 그 중 하나는 컴파일러가 될 것입니다. 이제 저는 이번 분기 내내 과제를 생각하면서이 문제를 겪었습니다. 그러나 나는 그것을 예를 들어서 대신 사용할 수있게되었습니다.

public class DeadCode {
  public static void main(String[] args) {
     return;
     System.out.println("This line won't print.");
  }
}

위의 프로그램에서 print 문은로 인해 실행되지 않습니다 return. 컴파일러는 때때로 데드 코드에 대한 경고 또는 오류를 표시합니다. 예를 들어, 위의 코드는 Java로 컴파일되지 않습니다. 그러나 javac 컴파일러는 모든 프로그램에서 데드 코드의 모든 인스턴스를 감지하지는 않습니다. 컴파일러가 그렇게 할 수 없다는 것을 어떻게 증명할 수 있습니까?


29
당신의 배경은 무엇이며 당신이 가르 칠 맥락은 무엇입니까? 무뚝뚝하게, 나는 당신이 가르 칠 것을 보면서 당신이 이것을 물어봐야 할 것이 약간 걱정됩니다. 그러나 여기에 묻는 좋은 전화!
Raphael


9
@ MichaelKjörling 죽은 코드 감지는 이러한 고려 사항이 없어도 불가능합니다.
David Richerby

2
BigInteger i = 0; while(isCollatzConjectureTrueFor(i)) i++; printf("Hello world\n");
user253751

2
@immibis이 질문은 데드 코드 감지가 불가능하다는 증거 를 요구합니다 . 올바른 데드 코드 감지가 수학에서 열린 문제를 해결해야하는 예를 제시했습니다 . 데드 코드 감지가 불가능하다는 것을 증명 하지는 않습니다 .
David Richerby

답변:


57

그것은 모두 정지 문제의 결정 불가능 성에서 비롯됩니다. "완벽한"데드 코드 기능, 일부 Turing Machine M 및 일부 입력 문자열 x 및 다음과 같은 프로 시저가 있다고 가정하십시오.

Run M on input x;
print "Finished running input";

M이 영원히 실행되면 도달하지 않으므로 print 문을 삭제합니다. M이 영원히 실행되지 않으면 print 문을 유지해야합니다. 따라서 데드 코드 리무버가 있으면 Halting Problem을 해결할 수 있으므로 데드 코드 리무버가 없다는 것을 알 수 있습니다.

우리가이 문제를 해결하는 방법은 "보수적 근사치"입니다. 따라서 위의 Turing Machine 예제에서 x에서 M을 실행하면 끝날 수 있다고 가정 할 수 있으므로 안전하게 재생하고 인쇄 명령문을 제거하지 마십시오. 귀하의 예에서, 우리는 어떤 기능이 멈추거나 멈추지 않더라도 그 인쇄 문에 도달 할 방법이 없다는 것을 알고 있습니다.

일반적으로 이것은 "제어 흐름 그래프"를 구성하여 수행됩니다. 우리는 영원히 실행되거나 한 번만 실행되고 둘 다를 방문하지 않더라도 "while 루프의 끝이 시작과 이후에 연결됩니다"와 같은 간단한 가정을합니다. 마찬가지로, if 문이 실제로 사용되지 않더라도 if 문이 모든 분기에 도달 할 수 있다고 가정합니다. 이러한 종류의 단순화를 통해 사용자가 제공 한 예제와 같이 "명백하게 죽은 코드"를 제거하면서도 결정 가능한 상태를 유지할 수 있습니다.

의견에서 몇 가지 혼란을 명확히하기 위해 :

  1. Nitpick : 고정 M의 경우 항상 결정 가능합니다. M은 입력이어야합니다

    Raphael이 말했듯이, 우리 예제에서는 Turing Machine을 입력으로 간주합니다. 아이디어는 완벽한 DCE 알고리즘 이 있다면 Turing Machine에 제공하는 코드 스 니펫을 구성 할 수 있으며 DCE를 사용하면 중지 문제를 해결할 수 있다는 것입니다.

  2. 확신하지 못했습니다. 분기없는 직선 실행에서 무딘 문장으로 반환하는 것은 결정하기 어렵지 않습니다. (그리고 내 컴파일러는 이것을 알아낼 수 있다고 말합니다)

    njzk2 제기 문제 : 당신은 절대적으로 맞습니다.이 경우 반환에 도달 한 후 진술이 불가능하다는 것을 결정할 수 있습니다. 제어 흐름 그래프 제약 조건을 사용하여 도달 가능성을 설명 할 수있을 정도로 간단하기 때문입니다 (즉, return 문에서 나가는 가장자리가 없음). 그러나 완전한 데드 코드 제거기가 없으므로 사용되지 않는 모든 코드가 제거됩니다.

  3. 나는 증거에 대한 입력 의존적 증거를 취하지 않습니다. 코드를 유한하게 만들 수있는 사용자 입력이있는 경우 컴파일러는 다음 분기가 종료되지 않았다고 가정하는 것이 맞습니다. 나는이 모든 투표가 무엇인지 알 수 없다. 그것은 명백하다 (예 : 끝없는 stdin).

    TomášZato의 경우 : 실제로 입력에 의존하는 증거는 아닙니다. 오히려 "전망"으로 해석하십시오. 다음과 같이 작동합니다. 완벽한 DCE 알고리즘이 있다고 가정합니다. 임의의 Turing Machine M과 입력 x를 제공하면 DCE 알고리즘을 사용하여 위의 코드 스 니펫을 구성하고 인쇄 문이 제거되었는지 확인하여 M이 중지되는지 여부를 결정할 수 있습니다. forall-statement를 증명하기 위해 임의의 매개 변수를 남겨 두는이 기술은 수학과 논리에서 일반적입니다.

    코드가 유한하다는 TomášZato의 요점을 완전히 이해하지 못합니다. 분명히 코드는 유한하지만 완벽한 DCE 알고리즘은 모든 코드에 적용되어야합니다. 이는 완전한 세트입니다. 마찬가지로 코드 자체는 유한하지만 코드의 잠재적 인 실행 시간과 마찬가지로 잠재적 인 입력 세트는 무한합니다.

    마지막 분기 비 망명을 고려할 때 : 내가 말한 "보수적 근사"측면에서 안전하지만 OP가 요구하는 데드 코드의 모든 인스턴스를 감지하는 것만으로는 충분하지 않습니다.

다음과 같은 코드를 고려하십시오.

while (true)
  print "Hello"
print "goodbye"

print "goodbye"프로그램의 동작을 바꾸지 않고 제거 할 수 있습니다 . 따라서 죽은 코드입니다. 대신 다른 함수 호출이 있다면 그러나 (true)while조건은 우리가 결정 불가능 성으로 이어지는, 그것을 제거하거나 할 수없는 경우, 우리는 알지 못한다.

나는 이것을 혼자서 내리지 않는다는 점에 유의하십시오. 컴파일러 이론에서 잘 알려진 결과입니다. 이 The Tiger Book 에서 논의되었습니다 . ( Google 도서 에서 이들이 어디에서 이야기하는지 확인할 수 있습니다 .


1
@ njzk2 : 우리는 죽은 코드 제거기를 구축하는 것은 불가능 보여주기 위해 노력하고 그을 제거해 모든 죽은 코드, 그것은 죽은 코드 제거기이 해소 구축하는 것은 불가능하지 않는 것이 몇 가지 죽은 코드를. 제어 흐름 그래프 기술을 사용하여 반환 후 인쇄 예제를 쉽게 제거 할 수 있지만 모든 데드 코드를 제거 할 수있는 것은 아닙니다.
user2357112는

4
이 답변은 의견을 참조 합니다. 답변을 읽으면서 댓글로 넘어 가서 답변으로 돌아 가야합니다. 이것은 혼란 스럽습니다 (아마도 주석이 깨지기 쉽고 손실 될 수 있다고 생각할 때). 독립적 인 답변은 훨씬 쉽게 읽을 수 있습니다.
TRiG

1
@ TomášZato-변수 을 증가시키고 n 이 홀수 완전 수 인지 여부를 확인 하고 그러한 숫자를 찾으면 종료 되는 프로그램을 고려하십시오 . 분명히이 프로그램은 외부 입력에 의존하지 않습니다. 이 프로그램의 종료 여부를 쉽게 결정할 수 있다고 주장하고 있습니까?
Gregory J. Puleo

3
엑스엑스

1
jmite, 답변이 자체적으로 유효하도록 올바른 주석을 답변에 포함하십시오. 그런 다음 쓸모없는 모든 주석에 플래그를 지정하여 정리할 수 있도록하십시오. 감사!
Raphael

14

이것은 비 종료에 대한 잠재적 혼란을 피하는 jmite의 답변을 왜곡 한 것입니다. 나는 항상 자신을 멈추고 죽은 코드를 가질 수는 있지만 알고리즘 적으로 결정할 수는없는 프로그램을 제공 할 것입니다.

데드 코드 식별자에 대한 다음 입력 클래스를 고려하십시오.

simulateMx(n) {
  simulate TM M on input x for n steps
  if M did halt
    return 0
  else
    return 1
}

M그리고 x고정되어 있기 때문에 if 가 멈추지 않는 경우에만 simulateMs데드 코드가 있습니다.return 0Mx

x 이 자체적으로 정지하지 않는 가 있습니다 암호.

따라서 데드 코드 검사는 계산할 수 없습니다.

이 맥락에서 증명 기술로 축소에 익숙하지 않은 경우 참조 자료를 권장 합니다 .


5

세부 사항에 얽매이지 않고 이러한 종류의 속성을 보여주는 간단한 방법은 다음과 같이 정리하는 것입니다.

Lemma : Turing-complete 언어의 컴파일러 C undecidable_but_true()에는 인수를 사용하지 않고 부울 true를 리턴 하는 함수가 있으므로 C undecidable_but_true()가 true 또는 false를 리턴 하는지 예측할 수 없습니다 .

함수는 컴파일러에 따라 다릅니다. 함수가 주어지면 undecidable_but_true1()컴파일러는이 함수가 true 또는 false를 반환하는지에 대한 지식으로 항상 보강 될 수 있습니다. 그러나 undecidable_but_true2()다루지 않을 다른 기능이 항상 있습니다.

증명 : 에 의해 쌀의 정리 , 재산 "사실이 함수가 리턴"결정 불가능하다. 따라서 정적 분석 알고리즘은 가능한 모든 기능에 대해이 특성을 결정할 수 없습니다.

Corollary : 컴파일러 C가 주어지면 다음 프로그램에는 감지 할 수없는 데드 코드가 포함됩니다.

if (!undecidable_but_true()) {
    do_stuff();
}

Java에 대한 참고 사항 : Java 언어에서는 컴파일러가 도달 할 수없는 코드가 포함 된 특정 프로그램을 거부하고 해당 코드가 모든 도달 가능한 지점에서 제공되는 것을 현명하게 요구합니다 (예 : 비 공여 기능의 제어 흐름은 return명령문으로 끝나야 함 ). 이 언어는 도달 할 수없는 코드 분석이 수행되는 방식을 정확하게 지정합니다. 그렇지 않다면 휴대용 프로그램을 작성하는 것이 불가능할 것입니다. 주어진 형태의 프로그램

some_method () {
    <code whose continuation is unreachable>
    // is throw InternalError() needed here?
}

도달 할 수없는 코드 뒤에 어떤 다른 코드가 와야하고 어떤 경우에 코드가 와서는 안되는 경우를 지정해야합니다. 도달 할 수 없지만 Java 컴파일러가 알 수없는 방식으로 코드를 포함하는 Java 프로그램의 예는 Java 101에 나타납니다.

String day_of_week(int n) {
    switch (n % 7) {
    case 0: return "Sunday";
    case 1: case -6: return "Monday";
    …
    case 6: case -1: return "Saturday";
    }
    // return or throw is required here, even though this point is unreachable
}

일부 언어의 일부 컴파일러는 끝에 day_of_week도달 할 수 없음 을 감지 할 수 있습니다 .
user253751

@immibis 예, 예를 들어 CS101 학생들은 내 경험에 따라이를 수행 할 수 있습니다 (CS101 학생들은 정적 분석기가 아니지만 일반적으로 부정적인 경우를 잊어 버립니다). 그것은 내 요지의 일부입니다. 이것은 Java 컴파일러가 감지하지 못하는 (적어도 경고 할 수는 있지만 거부 할 수없는) 도달 할 수없는 코드가있는 프로그램의 예입니다.
질 'SO-정지 존재 악마'

1
나는 Lemma의 어구가 잘못되어 색조가 잘못되어 두려워합니다. 결정 불가능 성은 (무한한) 인스턴스 세트의 용어로 표현하는 경우에만 의미가 있습니다. (컴파일러 모든 함수에 대한 해답을 생성하며, 항상 정확하지는 않지만 결정 불가능한 단일 인스턴스가 존재하지 않는다고 말합니다.) Lemma와 Proof 사이의 단락 (Lemma와 완전히 일치하지 않음) 언급 한 바와 같이)이 문제를 해결하려고 시도하지만 명확하게 올바른 음모를 공식화하는 것이 좋습니다.
Raphael

@Raphael 어? 아니요, 컴파일러는“이 함수가 상수입니까?”라는 질문에 대한 답을 제시 할 필요는 없습니다. 작동 코드를 생성하기 위해“모름”과“알지 못함”을 구분할 필요는 없지만 코드 변환 부분이 아닌 컴파일러의 정적 분석 부분에만 관심이 있기 때문에 여기서는 관련이 없습니다. 나는 "컴파일러"대신에 "정적 분석기"를 작성해야한다는 것이 아니라면, 당신의 주장에 대한 오해의 소지가 있거나 부정확 한 것이 무엇인지 이해하지 못합니까?
Gilles 'SO- 악마 중지

이 문장은 "결정 불가능 성"은 해결할 수없는 인스턴스가 있음을 의미합니다. (나는 당신이 그런 말을하는 것을 의미하지 않는다는 것을 알고 있지만, 그것이 경고 / 불경을 읽을 수있는 방법입니다, imho)
Raphael

3

jmite의 답변은 프로그램이 계산을 종료할지 여부에 적용됩니다. 무한이기 때문에 코드가 죽은 후에는 호출하지 않습니다.

그러나 또 다른 접근법이 있습니다. 답변이 있지만 알려지지 않은 문제입니다.

public void Demo()
{
  if (Chess.Evaluate(new Chessboard(), int.MaxValue) != 0)
    MessageBox.Show("Chess is unfair!");
  else
    MessageBox.Show("Chess is fair!");
}

public class chess
{
  public Int64 Evaluate(Chessboard Board, int SearchDepth)
  {
  ...
  }
}

의심 할 여지없이이 루틴 에는 데드 코드 포함되어 있습니다. 함수는 한 경로는 실행하지만 다른 경로는 실행하지 않는 응답을 반환합니다. 그래도 행운을 빕니다! 나의 기억은 우주의 수명 안에서 이것을 해결할 수있는 이론적 인 컴퓨터가 아니다.

더 자세하게:

Evaluate()기능은 양측이 완벽하게 검색 할 경우 (최대 검색 깊이로) 어느 쪽이 체스 게임에서이기는지를 계산합니다.

체스 평가자들은 일반적으로 가능한 모든 깊이의 이동을 미리 본 다음 해당 지점에서 보드 점수를 매기려고 시도합니다 (때로는 교환 등을 통해 중간 지점을 더 멀리 볼 때 특정 지점을 확장하면 인식이 매우 왜곡 될 수 있습니다). 검색이 철저한 17695 절반 이동이며, 가능한 모든 체스 게임을 통과합니다. 모든 게임이 끝나기 때문에 각 보드의 위치를 ​​결정하는 데 문제가 없으므로 보드 평가 논리를 볼 이유가 없습니다. 이는 결코 호출되지 않을 것이므로 결과는 승리, 손실 또는 무승부. 결과가 무승부 인 경우 게임은 공정하고 결과가 무승부 인 경우 불공평 한 게임입니다. 조금 확장하면 다음과 같이됩니다.

public Int64 Evaluate(Chessboard Board, int SearchDepth)
{
  foreach (ChessMove Move in Board.GetPossibleMoves())
    {
      Chessboard NewBoard = Board.MakeMove(Move);
      if (NewBoard.Checkmate()) return int.MaxValue;
      if (NewBoard.Draw()) return 0;
      if (SearchDepth == 0) return NewBoard.Score();
      return -Evaluate(NewBoard, SearchDepth - 1);
    }
}

또한 컴파일러가 Chessboard.Score ()가 죽은 코드임을 인식하는 것은 사실상 불가능합니다. 체스의 규칙에 대한 지식은 우리 인간이 이것을 알아낼 수있게하지만 이것을 알아 내려면 MakeMove가 조각 수를 늘릴 수 없으며 조각 수가 너무 오랫동안 정적 상태로 유지되면 Chessboard.Draw ()가 true를 반환한다는 것을 알아야합니다. .

검색 깊이는 전체 이동이 아니라 절반 이동입니다. 이것은 O (x ^ n) 루틴이기 때문에 이러한 종류의 AI 루틴에는 정상입니다. 하나 이상의 검색 플라이를 추가하면 실행 시간에 큰 영향을 미칩니다.


8
점검 알고리즘이 계산을 수행해야한다고 가정합니다. 일반적인 오류입니다! 아니오, 체커가 어떻게 작동하는지에 대해 아무 것도 가정하지 않아도됩니다 . 그렇지 않으면 그 존재를 반박 할 수 없습니다.
Raphael

6
질문 은 죽은 코드를 감지 하는 것이 불가능하다는 증거 를 요구합니다 . 귀하의 게시물에는 귀하가 사례의 예가 포함되어 있습니다 의심 이 될 것이다 어려운 죽은 코드를 탐지합니다. 그것은 당면한 질문에 대한 답이 아닙니다.
David Richerby

2
@LorenPechtel 몰라요, 그러나 이것은 증거가 아닙니다. 여기도 참조 ; 오해의 확실한 예.
Raphael

3
그것이 도움이된다면, 우주의 수명 이상으로 누군가 컴파일러를 실행하는 것을 이론적으로 막는 것이 아무것도 없다고 생각하십시오. 유일한 한계는 실용성입니다. 결정 가능한 문제는 복잡성 클래스 NONELEMENTARY에 있더라도 결정 가능한 문제입니다.
가명

4
다시 말해,이 답변은 기껏해야 휴리스틱으로 모든 죽은 코드를 감지하는 컴파일러를 만드는 것이 쉽지 않은 이유를 보여 주지만 불가능하다는 증거는 아닙니다. 이런 종류의 예 학생들을 위해 직관을 구축하는 방법으로 유용 수 있지만 그 증거는 아닙니다. 스스로를 증거로 제시함으로써 장애를 일으 킵니다. 대답은 직관 구축의 예이지만 불가능한 증거가 아니라는 것을 나타내도록 편집해야합니다 .
DW

-3

컴퓨팅 과정에서 데드 코드 개념은 컴파일 시간과 런타임 간의 차이를 이해하는 데 흥미가 있다고 생각합니다!

컴파일러는 컴파일 타임 시나리오에서 통과 할 수없는 코드가있는 시점을 결정할 수 있지만 런타임에는 그렇게 할 수 없습니다. 루프 브레이크 테스트를위한 사용자 입력이있는 간단한 while 루프가이를 보여줍니다.

만약 컴파일러가 실제로 런타임 데드 코드 (즉, 식별 된 Turing 완료)를 결정할 수 있다면, 작업이 이미 완료 되었기 때문에 코드를 실행할 필요가 없다는 주장이 있습니다!

컴파일 타임 데드 코드 검사를 통과하는 코드가 존재하면 실제 프로젝트의 실제 입력에서 입력 및 일반 코딩 위생에 대한 실용적인 경계 검사가 필요하다는 것을 알 수 있습니다.


1
이 질문은 죽은 코드를 감지하는 것이 불가능하다는 증거를 요구합니다. 당신은 그 질문에 대답하지 않았습니다.
David Richerby

또한 "컴파일러가 컴파일 타임 시나리오를 통과 할 수없는 코드가있을 때 컴파일러가 결정할 수 있음"에 대한 주장은 정확하지 않으며 질문에서 요구하는 내용과 직접 모순됩니다.
David Richerby

@David Richerby, 당신이 나를 오해하고 있다고 생각합니다. 컴파일 타임 검사가 모든 죽은 코드를 찾을 수 있다고 제안하지는 않습니다. 컴파일 타임에 식별 할 수있는 모든 데드 코드 세트의 하위 세트가 있다고 제안합니다. if (true == false) {print ( "something");}를 작성하면 컴파일 타임에 해당 인쇄 명령문을 식별하여 데드 코드가 될 수 있습니다. 이것이 당신의 주장에 대한 반례라는 것에 동의하지 않습니까?
dwoz 2016

물론, 일부 죽은 코드를 확인할 수 있습니다 . 그러나 당신이 자격없이 "[죽은 코드를 가지고있는시기]를 결정하라"라고 말할 것이라면, 그것은 나에게있어, 단지 죽은 코드를 전부는 아니라는 것을 의미합니다.
David Richerby
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.