소스 코드에서만 메모리 할당 해제 시점을 정적으로 예측할 수 있습니까?


27

메모리 (및 리소스 잠금)는 프로그램 실행 중 결정적인 지점에서 OS로 반환됩니다. 프로그램 자체의 제어 흐름은 주어진 리소스를 할당 해제 할 수있는 위치를 알기에 충분합니다. 인간 프로그래머가 fclose(file)프로그램을 수행 할 때 작성 위치를 알고있는 것처럼.

GC는 제어 흐름이 실행될 때 런타임 중에 직접 파악하여이 문제를 해결합니다. 그러나 제어 흐름에 대한 진실의 근원은 근원입니다. 따라서 이론적으로 free()소스 (또는 AST)를 분석하여 컴파일 전에 호출 을 삽입 할 위치를 결정할 수 있어야합니다 .

레퍼런스 카운팅은이를 구현하는 확실한 방법이지만, 포인터가 여전히 참조 (아직 범위 내)이지만 더 이상 필요하지 않은 상황이 발생하기 쉽습니다. 이것은 포인터를 수동으로 할당 해제하는 책임을 해당 포인터에 대한 범위 / 참조를 수동으로 관리하는 책임으로 변환합니다.

프로그램 소스를 읽을 수있는 프로그램을 작성하는 것이 가능해 보입니다.

  1. 프로그램 제어 흐름의 모든 순열을 예측하여 프로그램의 실시간 실행을 보는 것과 유사한 정확도
  2. 할당 된 자원에 대한 모든 참조를 추적
  3. 각 참조에 대해 참조가 절대 역 참조되지 않는 가장 빠른 지점을 찾기 위해 전체 후속 제어 흐름을 통과합니다.
  4. 이 시점에서 해당 소스 코드 줄에 할당 해제 문을 삽입하십시오.

이 작업을 이미 수행하고있는 것이 있습니까? Rust 또는 C ++ 스마트 포인터 / RAII가 같은 것이라고 생각하지 않습니다.


57
정지 문제를 찾아보십시오. "컴파일러가 X를 수행하는지 알아낼 수 없는가?"라는 질문의 이유는 할아버지입니다. "일반적인 경우는 아님"으로 항상 답변됩니다.
래칫 괴물

18
메모리 (및 리소스 잠금)는 프로그램 실행 중 결정적인 지점에서 OS로 반환됩니다. No.
Euphoric

9
@ratchetfreak 고마워요. 화학 중단 대신 이학 학사 학위를 받았으면하는이 멈춤 문제와 같은 것을 알지 못합니다.
zelcon

15
@ zelcon5, 당신은 지금 화학에 대해 알고 중단 문제 ... :)
데이비드 아르노

7
@Euphoric 프로그램을 구성하지 않는 한 자원이 사용되는 경우의 경계가 RAII 또는 자원과의 시도와 같이 매우 명확합니다
ratchet freak

답변:


23

이 (고려 된) 예제를 보자.

void* resource1;
void* resource2;

while(true){

    int input = getInputFromUser();

    switch(input){
        case 1: resource1 = malloc(500); break;
        case 2: resource2 = resource1; break;
        case 3: useResource(resource1); useResource(resource2); break;
    }
}

언제 무료로 전화해야합니까? malloc에 ​​할당하기 전에에 resource1복사 할 수 없기 때문에 할 수 없으며 resource2, 할당하기 전에 resource2개입하지 않고 사용자로부터 2를 두 번 받았기 때문에 할 수 없습니다.

유일하게 확인하는 유일한 방법은 resource1과 resource2를 테스트하여 1과 2의 경우 같지 않은지 확인하고 그렇지 않은 경우 이전 값을 해제하는 것입니다. 이것은 단지 2 개의 가능한 참조 만이 있다는 것을 알고있는 참조 카운트입니다.


실제로 이것이 유일한 방법은 아닙니다. 다른 방법은 하나의 사본 만 존재 하도록 허용 하는 것입니다. 물론 이것은 자체 문제가 있습니다.
Jack Aidley

27

RAII는 자동으로 동일한 것은 아니지만 효과는 동일합니다. "더 이상 액세스 할 수없는 시점을 어떻게 알 수 있습니까?"라는 질문에 쉽게 대답 할 수 있습니다. 특정 자원이 사용될 때 영역을 커버하기 위해 범위 를 사용하여 .

비슷한 문제 "런타임에 프로그램 오류가 발생하지 않는지 어떻게 알 수 있습니까?" 이에 대한 해결책은 없는 등의 오류가있을 수 없음을 증명하는 프로그램을 통해하지만 형의 주석과 추론의 시스템을 사용하여 모든 실행 경로를 예측. 녹은이 증명 속성을 메모리 할당으로 확장하려는 시도입니다.

정지 문제를 해결하지 않고 프로그램 동작에 대한 증거를 작성할 수 있지만, 어떤 종류의 주석을 사용하여 프로그램을 제한하는 경우에만 가능합니다. 보안 증명 (sel4 등)도 참조하십시오.


의견은 긴 토론을위한 것이 아닙니다. 이 대화는 채팅 으로 이동 되었습니다 .
maple_shaft

13

예, 이것은 야생에 존재합니다. ML Kit 는 사용 가능한 메모리 관리 옵션 중 하나로 전략을 설명하는 프로덕션 품질의 컴파일러입니다. 또한 기존 GC를 사용하거나 참조 카운팅과 혼성화 할 수 있습니다 (힙 프로파일 러를 사용하여 실제로 어떤 전략이 프로그램에 가장 적합한 결과를 산출하는지 확인할 수 있습니다).

리전 기반 메모리 관리에 대한 회고 는 ML Kit의 최초 작성자가 성공 및 실패를 기록한 기사입니다. 최종 결론은 힙 프로파일 러의 도움을 받아 작성할 때 전략이 실용적이라는 것입니다.

(이것은 실제 공학 질문에 대한 답변을 위해 일반적으로 Halting Problem을 보지 않아야하는 이유를 잘 보여줍니다. 우리는 가장 현실적인 프로그램의 일반적인 경우를 원치 않거나 해결할 필요 가 없습니다 .)


5
이것이 Halting Problem을 올바르게 적용한 훌륭한 예라고 생각합니다. 중지 문제는 일반적인 경우 문제를 해결할 수 없음을 나타내므로 문제를 해결할 수있는 제한된 시나리오를 찾으십시오.
Taemyr

문제가 멀리하게하는 것이 주 우리가 표준 ML 및 하스켈과 같은 순수한 또는 거의 순수한 기능, 비 측 초래 언어에 대해 이야기 할 때 풀수
고양이

10

프로그램 제어 흐름의 모든 순열을 예측

이것이 문제가있는 곳입니다. 사소하지 않은 프로그램에 대해서는 순열의 양이 너무 커서 (실제로는 무한대), 필요한 시간과 메모리가이를 완전히 비실용적으로 만들 수 있습니다.


좋은 지적. 만약 내가 있다면 양자 프로세서가 유일한 희망이라고 생각한다
zelcon

4
@ zelcon5 하하, 아니. 양자 컴퓨팅은 이것을 더 나쁘게 만들지 않고 더 나쁘게 만듭니다 . 추가 ( "숨겨진") 변수를 프로그램에 추가하고 훨씬 더 불확실합니다. 내가 본 가장 실용적인 QC 코드는 "빠른 계산을위한 양자, 확인을위한 고전"에 의존합니다. 나는 퀀텀 컴퓨팅에 대해 거의 흠집을 내지 않았지만, 퀀텀 컴퓨터는 백업하고 결과를 확인하는 데 고전적인 컴퓨터가 없다면 그다지 유용하지 않을 것 같습니다.
Luaan

8

정지 문제는 이것이 모든 경우에 가능하지 않다는 것을 증명합니다. 그러나 여전히 많은 경우에 가능하며 실제로는 대부분의 변수에 대해 거의 모든 컴파일러가 수행합니다. 이것은 컴파일러가 장기 힙 스토리지 대신 스택 또는 레지스터에 변수를 할당하는 것이 안전하다는 것을 알려주는 방법입니다.

순수 함수 또는 소유권 의미론이 좋은 경우 정적 분석을 추가로 확장 할 수 있지만 코드에 더 많은 분기를 추가하는 것은 엄청나게 비용이 많이 듭니다.


컴파일러 는 메모리를 비울있다고 생각 합니다. 그러나 그렇지 않을 수도 있습니다. 포인터 나 지역 변수에 대한 참조를 반환하는 일반적인 초보자의 오류를 생각하십시오. 사소한 경우는 컴파일러에 의해 포착됩니다. true; 덜 사소한 것은 아닙니다.
피터-모니카 복원 복원

프로그래머 가 메모리 할당 @Peter를 수동으로 관리해야하는 언어의 프로그래머 는 실수를 저지 릅니다. 컴파일러가 메모리 할당을 관리 할 때 이러한 종류의 실수는 발생하지 않습니다.
Karl Bielefeldt

C 컴파일러를 포함해야하는 "거의 모든 컴파일러"라는 문구를 포함하여 매우 일반적인 진술을 작성했습니다.
피터-모니카 복원 복원

2
C 컴파일러는이를 사용하여 레지스터에 할당 할 수있는 임시 변수를 결정합니다.
Karl Bielefeldt

4

단일 프로그래머 또는 팀이 전체 프로그램을 작성하는 경우 메모리 (및 기타 리소스)를 비워야하는 위치에서 설계 지점을 식별 할 수있는 것이 합리적입니다. 따라서보다 제한된 상황에서는 설계의 정적 분석이 충분할 수 있습니다.

그러나 타사 DLL, API, 프레임 워크를 고려하고 스레드를 처리하는 경우 사용 프로그래머가 어떤 엔티티가 어떤 메모리를 소유하고 있는지에 대해 올바르게 추론하는 것은 매우 어려울 수 있습니다 (모든 경우에 불가능합니다) 그것을 마지막으로 사용했을 때. 우리가 일반적으로 사용하는 언어는 객체와 배열의 메모리 소유권이 얕고 깊다는 것을 충분히 문서화하지 못했습니다. 프로그래머가 (정적으로 또는 동적으로) 그것을 추론 할 수 없다면 컴파일러는 그럴 수도 없습니다. 다시 말하지만, 메모리 소유권 전송은 메소드 호출이나 인터페이스 등에서 캡처되지 않기 때문에 코드에서 메모리를 해제 할 시점과 위치를 정적으로 예측할 수 없습니다.

이것은 심각한 문제이므로 많은 현대 언어는 가비지 수집을 선택합니다. 가비지 수집은 마지막 실시간 참조 후 언젠가 자동으로 메모리를 회수합니다. GC의 성능 비용은 특히 (실시간 응용 프로그램의 경우) 상당히 크지 만 보편적 인 치료법은 아닙니다. 또한 GC를 사용하여 메모리 누수가 계속 발생할 수 있습니다 (예 : 증가하는 컬렉션). 그러나 이것은 대부분의 프로그래밍 연습에 좋은 솔루션입니다.

몇 가지 대안이 있습니다 (일부 부상).

Rust 언어는 RAII를 극한으로 만듭니다. 클래스와 인터페이스의 메소드에서 소유권을 이전하는 방법, 예를 들어, 발신자와 수신자 사이에 또는 이전보다 긴 객체에서 차용되는 객체를 더 자세하게 정의하는 언어 구조를 제공합니다. 메모리 관리에 대해 높은 컴파일 시간 안전성을 제공합니다. 그러나 선택하는 것은 사소한 언어가 아니며 문제가없는 것도 아닙니다 (예를 들어 디자인이 완전히 안정적이라고 생각하지 않으며 특정 것들이 여전히 실험 중이므로 변경되고 있다고 생각합니다).

Swift와 Objective-C는 또 다른 경로를 사용합니다. 이는 대부분 자동 참조 횟수입니다. 참조 카운트는 사이클과 관련된 문제를 야기하며, 특히 폐쇄와 같은 심각한 프로그래머 문제가 있습니다.


3
물론 GC에는 비용이 있지만 성능상의 이점도 있습니다. 예를 들어, .NET에서 "스택 할당"패턴을 사용하기 때문에 힙에서 할당하는 것은 거의 무료입니다. 포인터를 늘리면됩니다. 수동 메모리 할당을 사용하는 것보다 .NET GC에서 더 빨리 다시 작성된 응용 프로그램을 보았습니다. 실제로 명확하지 않습니다. 마찬가지로, 참조 카운팅은 실제로 상당히 비싸고 (GC와 다른 곳에서만), 피할 수 있다면 지불하고 싶지 않은 것입니다. 실시간 성능을 원한다면 여전히 정적 할당이 유일한 방법입니다.
Luaan

2

프로그램이 알려지지 않은 입력에 의존하지 않는다면 가능해야합니다 (복잡한 작업 일 수 있고 시간이 오래 걸릴 수 있다는 경고가 있지만 프로그램에서도 마찬가지입니다). 이러한 프로그램은 컴파일 타임에 완전히 해결할 수 있습니다. C ++ 용어로, (거의) 완전히 constexprs 로 구성 될 수 있습니다 . 간단한 예는 pi의 처음 100 자리를 계산하거나 알려진 사전을 정렬하는 것입니다.


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