[compiler-theory] 컴파일러가 데드 코드 감지를 완전히 해결할 수없는 이유는 무엇입니까?

C 또는 Java에서 사용한 컴파일러는 데드 코드 방지 기능이 있습니다 (라인이 실행되지 않을 때 경고). 교수님은 컴파일러가이 문제를 완전히 해결할 수는 없다고 말합니다. 왜 그런지 궁금했습니다. 이론 기반 클래스이므로 컴파일러의 실제 코딩에 너무 익숙하지 않습니다. 그러나 나는 그들이 무엇을 점검하는지 (예 : 가능한 입력 문자열 대 허용되는 입력 등) 궁금한 이유가 무엇인지 궁금했습니다.



답변

데드 코드 문제는 Halting 문제 와 관련이 있습니다 .

Alan Turing은 프로그램에 제공 될 일반 알고리즘을 작성하는 것이 불가능하며 해당 프로그램이 모든 입력에 대해 중지되는지 여부를 결정할 수 있음을 증명했습니다. 특정 유형의 프로그램에 대해서는 이러한 알고리즘을 작성할 수 있지만 모든 프로그램에 대해 해당되는 것은 아닙니다.

이것이 죽은 코드와 어떤 관련이 있습니까?

앞뒤가 맞지 않는 문제는 환원 죽은 코드를 찾는 문제이다. 즉, 모든 프로그램 에서 데드 코드를 감지 할 수있는 알고리즘을 찾으면 해당 알고리즘을 사용 하여 프로그램이 중지 되는지 테스트 할 수 있습니다 . 이것이 불가능하다는 것이 입증되었으므로 데드 코드에 대한 알고리즘 작성도 불가능합니다.

죽은 코드에 대한 알고리즘을 Halting 문제에 대한 알고리즘으로 어떻게 전송합니까?

간단 함 : 중지하려는 프로그램이 끝난 후 코드 줄을 추가합니다. 데드 코드 감지기가이 라인이 죽은 것을 감지하면 프로그램이 중지되지 않는다는 것을 알 수 있습니다. 그렇지 않은 경우 프로그램이 중지됨을 알 수 있습니다 (마지막 줄로 이동 한 다음 추가 된 코드 줄로 이동).


컴파일러는 일반적으로 컴파일 타임에 죽었 음을 입증 할 수있는 것들을 확인합니다. 예를 들어, 컴파일 타임에 거짓으로 판단 될 수있는 조건에 의존하는 블록. 또는 return(같은 범위 내에서) 뒤에 나오는 진술 .

이들은 특정한 경우이므로 알고리즘을 작성할 수 있습니다. 조건이 구문 상 모순인지 여부를 확인하여 항상 거짓을 반환하는 알고리즘과 같이 더 복잡한 경우에 대한 알고리즘을 작성할 수는 있지만 모든 가능한 경우를 다루지는 않습니다.


답변

자, 정지 문제의 결정 불가능에 대한 고전적인 증거를 가지고 정지 검출기를 데드 코드 검출기로 변경합시다!

C # 프로그램

using System;
using YourVendor.Compiler;

class Program
{
    static void Main(string[] args)
    {
        string quine_text = @"using System;
using YourVendor.Compiler;

class Program
{{
    static void Main(string[] args)
    {{
        string quine_text = @{0}{1}{0};
        quine_text = string.Format(quine_text, (char)34, quine_text);

        if (YourVendor.Compiler.HasDeadCode(quine_text))
        {{
            System.Console.WriteLine({0}Dead code!{0});
        }}
    }}
}}";
        quine_text = string.Format(quine_text, (char)34, quine_text);

        if (YourVendor.Compiler.HasDeadCode(quine_text))
        {
            System.Console.WriteLine("Dead code!");
        }
    }
}

YourVendor.Compiler.HasDeadCode(quine_text)반환 false하면 라인 System.Console.WriteLn("Dead code!");이 실행되지 않으므로이 프로그램에는 실제로 데드 코드 있으며 검출기가 잘못되었습니다.

그러나이 값을 반환 true하면 라인 System.Console.WriteLn("Dead code!");이 실행되고 프로그램에 더 이상 코드가 없으므로 데드 코드가 없으므로 검출기가 잘못되었습니다.

따라서 “데드 코드가 있습니다”또는 “데드 코드가 없습니다”만 반환하는 데드 코드 감지기가 있습니다.


답변

중지 문제가 너무 모호한 경우이 방법으로 생각하십시오.

모든 양의 정수 n에 대해 참으로 여겨지 지만 모든 n에 대해 참으로 입증되지는 않은 수학적 문제를 취하십시오 . 좋은 예는 Goldbach의 추측 인데, 2보다 큰 양의 정수는 두 소수의 합으로 표현 될 수 있습니다. 그런 다음 (적절한 bigint 라이브러리를 사용하여)이 프로그램을 실행하십시오 (의사 코드는 다음과 같습니다).

 for (BigInt n = 4; ; n+=2) {
     if (!isGoldbachsConjectureTrueFor(n)) {
         print("Conjecture is false for at least one value of n\n");
         exit(0);
     }
 }

구현은 isGoldbachsConjectureTrueFor()독자에게는 연습으로 남겨 두지 만이 목적을 위해 모든 소수에 대한 간단한 반복이 될 수 있습니다.n

논리적으로 위의 내용은 다음과 같아야합니다.

 for (; ;) {
 }

(즉 무한 루프) 또는

print("Conjecture is false for at least one value of n\n");

골드 바흐의 추측은 사실이거나 사실이 아니어야합니다. 컴파일러가 항상 데드 코드를 제거 할 수 있다면 어느 경우 든 여기서 제거 할 수있는 데드 코드가있을 것입니다. 그러나 최소한 그렇게하면 컴파일러는 임의로 어려운 문제를 해결해야합니다. 우리는 문제를 제공 할 수 라도 유용 열심히 코드의 비트를 제거하는 결정하기 위해 (예를 들어, NP 완전 문제를) 해결해야 할 것이다. 예를 들어 우리가이 프로그램을한다면 :

 String target = "f3c5ac5a63d50099f3b5147cabbbd81e89211513a92e3dcd2565d8c7d302ba9c";
 for (BigInt n = 0; n < 2**2048; n++) {
     String s = n.toString();
     if (sha256(s).equals(target)) {
         print("Found SHA value\n");
         exit(0);
     }
 }
 print("Not found SHA value\n");

우리는 프로그램이 “발견 된 SHA 값”또는 “발견되지 않은 SHA 값”을 출력한다는 것을 알고 있습니다 (어느 것이 참인지 알려면 보너스 포인트). 그러나 컴파일러가 2 ^ 2048 반복의 순서를 취하는 합리적으로 최적화 할 수 있습니다. 실제로 위의 프로그램이 최적화없이 아무것도 인쇄하지 않고 우주의 열사병까지 실행될 것이라고 예측 했으므로 실제로는 큰 최적화가 될 것입니다.


답변

C ++ 또는 Java에 Eval유형 함수 가 있는지는 모르지만 많은 언어 에서 name으로 메소드 호출 할 수 있습니다 . 다음 (고려 된) VBA 예를 고려하십시오.

Dim methodName As String

If foo Then
    methodName = "Bar"
Else
    methodName = "Qux"
End If

Application.Run(methodName)

호출 할 메소드의 이름은 런타임까지 알 수 없습니다. 따라서 정의에 따라 컴파일러는 특정 메소드가 호출되지 않는다는 것을 절대 확실하게 알 수 없습니다.

실제로 이름으로 메서드를 호출하는 예를 보면 분기 논리가 필요하지 않습니다. 간단히 말해서

Application.Run("Bar")

컴파일러가 결정할 수있는 것 이상입니다. 코드가 컴파일 될 때 모든 컴파일러는 특정 문자열 값이 해당 메소드에 전달된다는 것을 알고 있습니다. 런타임까지 해당 메소드가 존재하는지 확인하지 않습니다. 더 일반적인 방법을 통해 다른 곳에서 메서드를 호출하지 않으면 죽은 메서드를 찾으려고 시도하면 오 탐지를 반환 할 수 있습니다. 리플렉션을 통해 코드를 호출 할 수있는 모든 언어에 동일한 문제가 있습니다.


답변

고급 컴파일러는 무조건 데드 코드를 감지하고 제거 할 수 있습니다.

그러나 조건부 데드 코드도 있습니다. 컴파일시 알 수 없으며 런타임 중에 만 감지 할 수있는 코드입니다. 예를 들어, 소프트웨어는 사용자 선호도에 따라 특정 기능을 포함하거나 제외하도록 구성하여 특정 시나리오에서 특정 코드 섹션을 죽인 것처럼 보일 수 있습니다. 그것은 실제 죽은 코드가 아닙니다.

테스트를 수행하고, 종속성을 해결하고, 조건부 데드 코드를 제거하고, 효율성을 위해 런타임에 유용한 코드를 재결합 할 수있는 특정 도구가 있습니다. 이것을 동적 데드 코드 제거라고합니다. 그러나 보시다시피 컴파일러의 범위를 벗어납니다.


답변

간단한 예 :

int readValueFromPort(const unsigned int portNum);

int x = readValueFromPort(0x100); // just an example, nothing meaningful
if (x < 2)
{
    std::cout << "Hey! X < 2" << std::endl;
}
else
{
    std::cout << "X is too big!" << std::endl;
}

이제 포트 0x100이 0 또는 1 만 반환하도록 설계되었다고 가정합니다.이 경우 컴파일러는 else블록이 절대 실행되지 않는다는 것을 알 수 없습니다 .

그러나이 기본 예에서 :

bool boolVal = /*anything boolean*/;

if (boolVal)
{
  // Do A
}
else if (!boolVal)
{
  // Do B
}
else
{
  // Do C
}

여기서 컴파일러는 else 블록이 죽은 코드임을 . 따라서 컴파일러는 데드 코드를 파악하기에 충분한 데이터가있는 경우에만 데드 코드에 대해 경고 할 수 있으며 주어진 블록이 데드 코드인지 알아 내기 위해 해당 데이터를 적용하는 방법을 알아야합니다.

편집하다

컴파일 타임에 데이터를 사용할 수없는 경우도 있습니다.

// File a.cpp
bool boolMethod();

bool boolVal = boolMethod();

if (boolVal)
{
  // Do A
}
else
{
  // Do B
}

//............
// File b.cpp
bool boolMethod()
{
    return true;
}

a.cpp를 컴파일하는 동안 컴파일러는 boolMethod항상을 반환 한다는 것을 알 수 없습니다 true.


답변

컴파일러에는 항상 일부 컨텍스트 정보가 없습니다. 예를 들어, double 값은 2를 절대로 두지 않는다는 것을 알고있을 것입니다. 이는 수학 함수의 기능이기 때문에 라이브러리에서 사용하기 때문입니다. 컴파일러는 라이브러리에서 코드를 볼 수 없으며 모든 수학적 함수의 모든 기능을 알 수 없으며이를 구현하는 길고 복잡한 모든 방법을 감지 할 수 없습니다.