메모리 관리되지 않는 프로그래밍의 복잡성은 무엇입니까?


24

다시 말해서, 자동 가비지 콜렉션은 어떤 특정 문제를 해결 했습니까? 저수준 프로그래밍을 한 번도 해본 적이 없으므로 리소스 확보가 얼마나 복잡한 지 알 수 없습니다.

GC가 해결하는 버그의 종류는 (적어도 외부 관찰자에게는) 자신의 언어, 라이브러리, 개념, 관용구 등을 잘 아는 프로그래머가하지 않는 종류의 것으로 보입니다. 그러나 나는 틀릴 수있다 : 수동 메모리 처리는 본질적으로 복잡합니까?


3
귀하의 질문에 위키 백과 문서 응답하지 않는 방법을 우리에게 얘기를 확장하십시오 garbace 수집 과에 더 구체적으로 섹션 혜택
야 니스

또 다른 이점은 보안입니다. 예를 들어 버퍼 오버런은 악용 될 수 있으며 메모리 관리 문제로 인해 많은 다른 보안 취약점이 발생합니다.
StuperUser

7
@StuperUser : 메모리의 원점과는 아무런 관련이 없습니다. GC에서 온 오버런 메모리를 버퍼링 할 수 있습니다. 일반적으로 GC 언어가이를 방지한다는 사실은 직교 적이며 GC 기술보다 30 년 미만인 언어는 버퍼 오버런 방지 기능을 제공하기 위해 이들 언어를 비교하고 있습니다.
DeadMG

답변:


29

저수준 프로그래밍을 한 번도 해본 적이 없으므로 리소스 확보가 얼마나 복잡한 지 알 수 없습니다.

"낮은 수준"의 정의가 시간이 지남에 따라 어떻게 변하는 지 재미있다. 프로그래밍을 처음 배웠을 때 간단한 할당 / 무료 패턴을 가능하게하는 표준화 된 힙 모델을 제공하는 모든 언어는 실제로 높은 수준으로 간주되었습니다. 에서 낮은 수준의 프로그래밍 , 당신은 메모리 자신의 트랙을 계속해야 (안 할당을하지만, 메모리 위치 자체!), 또는 당신이 정말로 멋진 느낌을한다면 자신의 힙 할당을 쓸 것입니다.

말했듯이, 실제로 그것에 대해 무섭거나 "복잡한"것은 전혀 없습니다. 당신이 어렸을 때 엄마가 장난감을 가지고 놀았을 때 장난감을 치워달라고 말한 것을 기억하십시오. 메모리 관리는 코드에 적용되는 것과 동일한 원칙입니다. (GC는 당신 청소 하는 하녀를 갖는 것과 같지만, 그녀는 매우 게으르고 약간의 단서가 없습니다.) 그 원리는 간단합니다. 코드의 각 변수에는 한 명의 소유자 만 있고, 그 소유자의 책임입니다 더 이상 필요하지 않은 변수의 메모리를 비 웁니다. ( 단일 소유권 원칙) 할당 당 한 번의 호출이 필요하며, 소유권 및 정리를 자동화하는 여러 가지 방법이 있으므로 해당 호출을 자신의 코드에 작성할 필요도 없습니다.

가비지 콜렉션은 두 가지 문제점을 해결해야합니다. 그것은 그들 중 하나에서 항상 매우 나쁜 일을하며, 구현에 따라 다른 것과 잘 맞지 않을 수도 있습니다. 문제는 메모리 누수 (완료된 후 메모리 유지)와 댕글 링 참조 (완료되기 전에 메모리 비우기)입니다. 두 가지 문제를 모두 살펴 보겠습니다.

매달려있는 참고 자료 :이 문제는 실제로 심각한 문제이므로 먼저 토론하십시오. 동일한 객체에 대한 두 개의 포인터가 있습니다. 당신은 그들 중 하나를 해제하고 다른 하나를 눈치 채지 못합니다. 그런 다음 나중에 두 번째 내용을 읽거나 쓰거나 해제하려고 시도합니다. 정의되지 않은 동작이 발생합니다. 눈치 채지 못하면 메모리가 쉽게 손상 될 수 있습니다. 가비지 콜렉션은 모든 참조가 사라질 때까지 아무것도 해제되지 않도록하여이 문제점을 불가능하게해야합니다. 완전히 관리되는 언어에서는 관리되지 않는 외부 메모리 리소스를 처리해야 할 때까지 거의 작동합니다. 그런 다음 바로 1로 돌아갑니다. 관리되지 않는 언어에서는 상황이 여전히 까다로워집니다. (모질라를 돌아 다니며

다행히이 문제를 다루는 것은 기본적으로 해결 된 문제입니다. 가비지 수집기가 필요하지 않으며 디버깅 메모리 관리자 가 필요합니다 . 예를 들어 Delphi를 사용하고 단일 외부 라이브러리와 간단한 컴파일러 지시문을 사용하여 할당자를 "Full Debug Mode"로 설정할 수 있습니다. 이는 사용 된 메모리를 추적하는 일부 기능을 활성화하기 위해 무시할 수있는 (5 % 미만) 성능 오버 헤드를 추가합니다. 객체를 비우면 메모리가 채워집니다.0x80바이트 (디버거에서 쉽게 인식 가능)를 해제하고 해제 된 객체에서 가상 메소드 (소멸자를 포함)를 호출하려고하면 객체가 생성 될 때 3 개의 스택 추적이있는 오류 상자가있는 프로그램을 발견하고 중단합니다. 그것이 해방되었을 때, 그리고 내가 지금 어디에 있는지 – 다른 유용한 정보와 더불어 예외를 일으킨다. 이것은 분명히 릴리스 빌드에는 적합하지 않지만 매달려있는 참조 문제를 추적하고 수정하는 것은 쉽지 않습니다.

두 번째 문제는 메모리 누수입니다. 더 이상 필요하지 않을 때 할당 된 메모리를 계속 유지할 때 발생합니다. 가비지 수집 유무에 관계없이 모든 언어로 발생할 수 있으며 코드를 올바르게 작성해야만 해결할 수 있습니다. 가비지 콜렉션은 아직 해제되지 않은 메모리 조각에 대한 유효한 참조가 없을 때 발생하는 특정 유형 의 메모리 누수 를 완화하는 데 도움이됩니다. 즉, 프로그램이 끝날 때까지 메모리가 할당 된 상태로 유지됩니다. 불행히도, 자동화 된 방식으로이 작업을 수행하는 유일한 방법은 모든 할당을 메모리 누수로 전환하는 것입니다!

그런 말을하려고하면 아마도 GC 지지자들에 의해 찌그러 질 것입니다. 메모리 누수의 정의는 더 이상 필요하지 않은 경우 할당 된 메모리를 유지합니다. 무언가에 대한 참조가없는 것 외에도, 해제해야 할 때 컨테이너 객체에 메모리를 보관하는 등 불필요한 참조를가함으로써 메모리가 누출 될 수 있습니다. 이 작업으로 인한 메모리 누수를 보았습니다. GC가 있는지 여부를 추적하는 것은 매우 어렵습니다. 메모리에 대한 완벽한 유효한 참조가 포함되어 있으며 디버깅 도구에 대한 명확한 "버그"가 없기 때문입니다. 잡기. 내가 아는 한, 이러한 유형의 메모리 누수를 잡을 수있는 자동화 된 도구는 없습니다.

따라서 가비지 수집기는 참조되지 않은 다양한 메모리 누수와 관련이 있습니다. 자동화 된 방식으로 처리 할 수있는 유일한 유형이기 때문입니다. 그것이 모든 참조에 대한 모든 참조를보고 그것을 가리키는 참조가없는 즉시 모든 객체를 자유롭게 할 수 있다면, 적어도 참조없는 문제와 관련하여 완벽 할 것입니다. 이를 자동화 된 방식으로 수행하는 것을 참조 카운팅 이라고 하며 일부 제한된 상황에서 수행 할 수 있지만 처리해야 할 자체 문제가 있습니다. (예를 들어, 개체 A에 대한 참조를 보유하는 개체 B에 대한 참조를 보유한 개체 A. 참조 계산 체계에서 A 또는 B에 대한 외부 참조가없는 경우에도 개체를 자동으로 해제 할 수 없습니다.) 가비지 수집기 사용 추적대신 : 시작 알려진 좋은 개체의 집합으로하는 모든 객체를 찾아, 그들이 참조하는 모든 오브젝트를 찾을 그들이 참조하고, 그래서 당신은 모든 것을 발견했습니다 재귀 때까지. 추적 프로세스에서 발견되지 않은 것은 쓰레기이므로 버릴 수 있습니다. (물론 이것을 성공적으로 수행하려면 추적 가비지 수집기가 항상 포인터처럼 보이는 임의의 메모리 조각과 참조 간의 차이를 항상 알 수 있도록 형식 시스템에 특정 제한을 적용하는 관리되는 언어가 필요합니다.)

추적에는 두 가지 문제점이 있습니다. 첫째, 속도가 느리고 진행되는 동안 경쟁 조건을 피하기 위해 프로그램이 다소 일시 중지되어야합니다. 이로 인해 프로그램이 사용자와 상호 작용하거나 서버 응용 프로그램의 성능이 저하 될 때 눈에 띄는 실행 딸꾹질이 발생할 수 있습니다. 할당을 처음 시도 할 때 할당이 수집되지 않으면 잠시 동안 고착 될 수 있다는 원칙에 따라 할당 된 메모리를 "세대"로 분할하는 등의 다양한 기술로이를 완화 할 수 있습니다. .NET 프레임 워크와 JVM 모두 세대 가비지 콜렉터를 사용합니다.

불행히도, 이것은 두 번째 문제로 들어갑니다. 메모리를 다 사용하면 메모리가 해제되지 않습니다. 객체로 작업을 마친 직후에 추적을 실행하지 않으면 다음 추적까지, 또는 1 세대를 지나서 더 길게 추적 될 수 있습니다. 사실, 내가 본 .NET 가비지 수집기의 가장 좋은 설명 중 하나 는 프로세스를 최대한 빨리 만들려면 GC가 가능한 한 오랫동안 수집을 연기해야한다고 설명합니다! 따라서 메모리 누수 문제는 가능한 한 오래 동안 가능한 많은 메모리를 누수 함으로써 기괴하게 "해결됩니다" ! 이것은 GC가 모든 할당을 메모리 누수로 바꾼다는 것을 의미합니다. 사실, 주어진 객체가된다는 보장이 없다 지금까지 수집.

필요할 때 메모리가 계속 재생 될 때 이것이 왜 문제입니까? 몇 가지 이유가 있습니다. 먼저, 많은 양의 메모리를 차지하는 큰 객체 (예 : 비트 맵)를 할당한다고 가정하십시오. 그런 다음 곧 완료되면 같은 양의 메모리를 사용하는 또 다른 큰 객체가 필요합니다. 첫 번째 개체가 해제되면 두 번째 개체는 메모리를 재사용 할 수 있습니다. 그러나 가비지 수집 시스템에서는 여전히 다음 추적이 실행되기를 기다리는 중이므로 두 번째 큰 객체에 불필요하게 메모리를 낭비하게됩니다. 기본적으로 경쟁 조건입니다.

둘째, 특히 대량으로 메모리를 불필요하게 보유하면 최신 멀티 태스킹 시스템에서 문제가 발생할 수 있습니다. 실제 메모리를 너무 많이 차지하면 프로그램이나 다른 프로그램이 페이지를 이동 (메모리의 일부를 디스크로 교체해야 함)하여 실제로 속도가 저하 될 수 있습니다. 서버와 같은 특정 시스템의 경우 페이징은 시스템 속도를 늦출뿐만 아니라 로드 상태 인 경우 전체를 중단시킬 수 있습니다.

댕글 링 참조 문제와 마찬가지로, 참조없는 문제는 디버깅 메모리 관리자로 해결할 수 있습니다. 필자는 Delphi의 FastMM 메모리 관리자에서 Full Debug Mode를 언급 할 것입니다. 가장 친숙하기 때문입니다. (다른 언어에서도 비슷한 시스템이 존재합니다.)

FastMM에서 실행중인 프로그램이 종료되면 해제되지 않은 모든 할당의 존재를 선택적으로보고하도록 할 수 있습니다. 전체 디버그 모드는 한 단계 더 나아갑니다. 할당 된 유형뿐만 아니라 할당 된 각 스택에 대한 스택 추적 및 기타 디버그 정보를 포함하는 디스크에 파일을 저장할 수 있습니다. 이렇게하면 참조없는 메모리 누수를 추적하는 것이 쉽지 않습니다.

실제로 보면 가비지 수집은 매달려있는 참조를 방지하는 데 도움이 될 수도 있고 그렇지 않을 수도 있으며 일반적으로 메모리 누수 처리에서 나쁜 작업을 수행합니다. 실제로 한 가지 미덕은 가비지 수집 자체가 아니라 부작용입니다. 힙 압축을 자동으로 수행하는 방법을 제공합니다. 이렇게하면 오랫동안 계속 실행되고 높은 수준의 메모리 변동이있는 프로그램을 중단시킬 수있는 가혹한 문제 (힙 조각화를 통한 메모리 소진)를 방지 할 수 있으며 가비지 수집없이 힙 압축이 거의 불가능합니다. 그러나 요즘 좋은 메모리 할당자는 버킷을 사용하여 조각화를 최소화하므로 극단적 인 상황에서는 조각화가 실제로 문제가됩니다. 힙 조각화가 문제가 될 수있는 프로그램의 경우 압축 가비지 수집기를 사용하는 것이 좋습니다. 그러나 다른 경우에 IMO는 가비지 수집을 사용하는 것이 조기 최적화이며 "해결하는"문제에 대한 더 나은 솔루션이 존재합니다.


5
나는이 답변을 좋아합니다-나는 계속해서 그것을 읽고 있습니다. 관련된 말을 할 수 없으므로 내가 말할 수있는 것은 모두 감사합니다.
vemv

3
예, GC는 메모리를 "최소한 시간 동안"누설하는 경향이 있지만 메모리 할당자가 수집 전에 메모리를 할당 할 수 없을 때 메모리를 수집하기 때문에 문제가되지 않습니다. GC 이외의 언어를 사용하면 누수가 항상 누수를 유지하므로 수집되지 않은 메모리가 너무 많아 실제로 메모리가 부족할 수 있습니다. "가비지 수집은 조기 최적화"... GC는 최적화가 아니며이를 염두에두고 설계되지 않았습니다. 그렇지 않으면 좋은 대답입니다.
토마스 에딩

7
@ThomasEding : GC는 확실히 최적화입니다. 성능 및 다양한 다른 프로그램 품질 메트릭을 희생 하면서 최소한의 프로그래머 노력을 최적화합니다 .
메이슨 휠러

5
모질라가 상당히 다른 결론을 내렸기 때문에 모질라의 버그 추적기를 한 번에 가리킨다는 것은 재밌습니다. Firefox는 메모리 관리 실수로 인해 수많은 보안 문제가 발생했습니다. 이것은 일단 탐지 된 오류를 해결하는 것이 얼마나 쉬운 지에 대한 것이 아닙니다. 일반적으로 개발자가 문제를 인식 할 때 손상이 이미 완료됩니다. Mozilla는 Rust 프로그래밍 언어에 정확하게 자금을 지원하여 이러한 오류가 처음에 발생하는 것을 방지합니다.

1
Rust는 가비지 콜렉션을 사용하지 않지만, 런타임시 오류를 감지하기 위해 디버거를 사용하지 않고 광범위한 컴파일 타임 검사만으로 Mason이 설명하는 방식을 정확하게 참조 계수를 사용합니다.
Sean Burton

13

C ++의 RAII와 같이 현재 널리 사용되는 시스템에서 사용되는 가비지 수집기와 같은 시대의 비가 비지 수집 메모리 관리 기술을 고려합니다. 이 접근 방식을 사용하면 자동화 된 가비지 콜렉션을 사용하지 않는 비용이 최소화되며 GC는 자체 문제점을 많이 발생시킵니다. 따라서, "별로"가 귀하의 문제에 대한 답변이라고 제안합니다.

사람들이 비 GC 생각하면, 그들은 생각, 기억 malloc하고 free. 그러나 이것은 거대한 논리적 오류입니다. 1970 년대 초 비 GC 리소스 관리를 90 년대 후반의 가비지 수집기와 비교할 것입니다. 이것은 분명히 사용에있을 때 쓰레기 수집 다소 불공정 comparison-입니다 mallocfree설계되었다 내가 제대로 기억한다면, 의미있는 프로그램을 실행하는 데 너무 느렸다. 예를 들어 unique_ptr, 모호하게 동등한 기간과 무언가를 비교하는 것이 훨씬 더 의미가 있습니다.

가비지 콜렉터는 참조주기를보다 쉽게 ​​처리 할 수 ​​있지만 이는 드문 경험입니다. 또한 GC는 모든 메모리 관리를 처리하므로 개발주기가 빨라질 수 있기 때문에 코드를 "던지기"할 수 있습니다.

반면, 자체 GC 풀을 제외한 다른 곳에서 온 메모리를 처리 할 때 엄청난 문제가 발생하는 경향이 있습니다. 또한 동시성을 사용하면 객체 소유권을 고려해야하기 때문에 많은 이점을 잃게됩니다.

편집 : 언급 한 많은 것들이 GC와 관련이 없습니다. 메모리 관리와 객체 지향이 혼란 스럽습니다. C ++과 같은 완전히 관리되지 않는 시스템에서 프로그래밍하는 경우 원하는만큼 범위 검사를 수행 할 수 있으며 표준 컨테이너 클래스가 제공합니다. 예를 들어 바운드 검사 또는 강력한 타이핑과 같은 GC는 없습니다.

언급 한 문제는 GC가 아닌 객체 지향으로 해결됩니다. 배열 메모리의 기원과 외부에 쓰지 않도록하는 것은 직교 개념입니다.

편집 : 고급 기술을 사용하면 모든 형태의 동적 메모리 할당이 필요하지 않습니다. 예를 들어, 동적 할당이 전혀없는 C ++에서 Y 조합을 구현하는 this 의 사용을 고려하십시오 .


여기에 대한 확장 된 토론이 정리되었습니다. 모든 사람이 채팅 하여 주제에 대해 더 자세히 논의 할 수 있다면 정말 감사하겠습니다.

@DeadMG, 콤비 네이터가 무엇을해야하는지 알고 있습니까? 결합해야합니다. 정의에 따르면, 결합기는 자유 변수가없는 함수입니다.
SK-logic

2
@ SK-logic : 나는 템플릿으로 순수하게 구현하고 멤버 변수가 없도록 선택할 수있었습니다. 그러나 클로저를 전달할 수 없으므로 유용성이 크게 제한됩니다. 채팅하러 올까?
DeadMG

@DeadMG, 정의가 명확합니다. 자유 변수가 없습니다. 나는 Y- 콤비 네이터를 정의하는 것이 가능하다면 (충분히 당신의 방식이 아닌) 어떤 언어 든 "충분히 기능적"이라고 생각합니다. 큰 "+"는 S, K 및 I 결합기를 통해 정의 할 수있는 경우입니다. 그렇지 않으면 언어가 충분히 표현되지 않습니다.
SK-logic

4
@ SK-logic : 친절한 중재자가 요청한 것처럼 채팅에 참여 하지 않으 시겠습니까? 또한 Y- 콤비 네이터는 Y- 콤비 네이터이며, 작업을 수행하거나 수행하지 않습니다. Y- 콤비 네이터의 Haskell 버전은 기본적으로이 버전과 동일합니다. 표현 된 상태가 숨겨져 있다는 것입니다.
DeadMG

11

가비지 수집 언어가 제공 할 것으로 예상되는 "자원 확보에 대해 걱정할 필요가없는 자유"는 상당 부분 환상입니다. 아무 것도 제거하지 않고 계속지도에 물건을 추가하면 곧 내가 말하는 것을 이해할 수 있습니다.

실제로 GCed 언어로 작성된 프로그램에서 메모리 누수는 매우 빈번합니다. 이러한 언어는 프로그래머를 게으르게 만들고 언어가 항상 (매직) 모든 객체를 처리한다는 잘못된 보안 감각을 갖기 때문입니다. 더 이상 생각할 필요가 없습니다.

가비지 콜렉션은 다른 고귀한 목표를 가진 언어에 필요한 기능입니다. 모든 것을 객체에 대한 포인터로 취급하고 동시에 프로그래머가 포인터라는 사실을 프로그래머로부터 숨겨 프로그래머가 커밋 할 수 없습니다. 포인터 산술 등을 시도하여 자살. 객체가되는 것은 GCed 언어가 GC가 아닌 언어보다 훨씬 더 자주 객체를 할당해야 함을 의미합니다. 즉, 프로그래머가 해당 객체를 할당 해제해야하는 부담이 가해지면 엄청나게 매력적이지 않습니다.

또한 가비지 수집은 프로그래머가 모든 코드를 할당 해제하기 위해 표현식을 별도의 명령문으로 나눌 필요없이 함수형 프로그래밍 방식으로 코드 내에서 타이트한 코드를 작성하고, 표현식 내에서 오브젝트를 조작 할 수있는 기능을 제공하는 데 유용합니다. 표현식에 참여하는 단일 객체.

이 모든 것 외에, 나는 나의 대답의 시작 부분에 "그것은 상당히 환상이다" 라고 썼음을 주목하시기 바랍니다 . 나는 그것이 환상 이라고 쓰지 않았습니다 . 나는 그것이 대부분 환상 이라고 쓰지 않았습니다 . 가비지 콜렉션은 프로그래머가 자신의 객체를 할당 해제하는 데 따르는 정신적 인 과제를 제거하는 데 유용합니다. 따라서 이런 의미에서 생산성 기능입니다.


4

가비지 콜렉터는 "버그"를 해결하지 않습니다. 일부 고급 언어 의미의 필수 부분입니다. GC를 사용하면 어휘 폐쇄와 같은 높은 수준의 추상화를 정의 할 수 있지만 수동 메모리 관리에서는 이러한 추상화가 유출되어 불필요하게 낮은 수준의 자원 관리에 바인딩됩니다.

의견에 언급 된 "단일 소유권 원칙"은 이러한 유출이 발생한 추상화의 좋은 예입니다. 개발자는 특정 기본 데이터 구조 인스턴스에 대한 링크 수에 전혀 신경을 쓰지 않아야합니다. 그렇지 않으면 코드가 직접 추가 할 수없는 수많은 추가 (코드 자체에 표시되지 않음) 제한 및 요구 사항이 없으면 코드가 일반적이고 투명하지 않습니다. . 이러한 코드는 더 높은 수준의 코드로 구성 될 수 없으며, 이는 책임 분리 원칙 (소프트웨어 엔지니어링의 주요 구성 요소 인 불행히도 대부분의 저수준 개발자가 전혀 존중하지 않는)의 계층을 어길 수없는 위반입니다.


1
@Mason Wheeler, C ++조차도 매우 제한된 형태의 클로저를 구현합니다. 그러나 거의 적절하고 일반적으로 사용 가능한 폐쇄는 아닙니다.
SK-logic

1
네가 틀렸어. GC는 스택 변수를 참조 할 수 없다는 사실로부터 사용자를 보호 할 수 없습니다. C ++에서는 "적절하고 자동으로 파괴되는 동적으로 할당 된 변수에 대한 포인터 복사"접근 방식을 사용할 수 있습니다.
DeadMG

1
@DeadMG, 코드가 다른 레벨을 통해 저수준 엔터티를 유출하는 것을 보지 못합니까?
SK-logic

1
@ SK-Logic : 좋습니다. 용어에 문제가 있습니다. "실제 폐쇄"에 대한 당신의 정의는 무엇이며, 델파이의 폐쇄는 할 수없는 무엇을 할 수 있습니까? (그리고 정의에 메모리 관리에 관한 내용을 포함시키는 것은 목표 게시물을 옮기는 것입니다. 구현 세부 정보가 아닌 동작에 대해 이야기합시다.)
Mason Wheeler

1
@ SK-Logic : ... 그리고 델파이의 클로저가 달성 할 수없는 간단한 형식화되지 않은 람다 클로저로 수행 할 수있는 예가 있습니까?
메이슨 휠러

2

실제로, 자신의 메모리를 관리하는 것은 하나의 잠재적 인 버그의 원인 일뿐입니다.

호출을 잊어 버린 경우 free(또는 사용중인 언어에 해당하는 언어가 무엇이든) 프로그램은 모든 테스트를 통과하지만 메모리가 누출 될 수 있습니다. 그리고 약간 복잡한 프로그램에서는에 대한 호출을 간과하기 쉽습니다 free.


3
놓친 free것이 최악이 아닙니다. 일찍이 free훨씬 더 파괴적입니다.
herby

2
그리고 더블 free!
quant_dev

헤헤! 위의 두 가지 의견 모두와 함께 갈 것입니다. 나는 이러한 범법 중 하나를 스스로 저지른 적이 없지만 (내가 아는 한) 그 효과가 얼마나 끔찍한 지 알 수 있습니다. quant_dev의 대답은 모든 것을 말합니다. 메모리 할당 및 할당 해제와 관련된 실수는 찾아서 고치기 어렵다는 악명이 높습니다.
Dawood는 Monica Monica를

1
이것은 잘못된 것입니다. "1970 년대 초"와 "1990 년 후반"을 비교하고 있습니다. 시점에 존재되는 GC를 malloc하고 free갈 수있는 비 GC의 방법이었다 아무것도 유용 할 것이 훨씬 너무 느린했다. RAII와 같은 최신 비 GC 접근 방식과 비교해야합니다.
DeadMG

2
@DeadMG RAII는 수동 메모리 관리가 아닙니다
quant_dev

2

수동 리소스는 지루할뿐만 아니라 디버깅하기도 어렵습니다. 다시 말해서, 그것을 올바르게 얻는 것이 지루할뿐만 아니라, 잘못했을 때 문제가 어디에 있는지 분명하지 않습니다. 이는 예를 들어 0으로 나누는 것과 달리 오류의 영향이 오류의 근원에서 멀어지고 점을 연결하는 데 시간,주의 및 경험이 필요하기 때문입니다.


1

가비지 수집은 하나의 큰 진보의 물결이 아닌 GC와 관련이없는 언어 개선에 대해 많은 신용을 얻는다고 생각합니다.

내가 아는 GC의 확실한 장점 중 하나는 프로그램에서 객체를 자유롭게 설정하고 모든 사람이 객체를 다 사용할 때 사라질 것이라는 것을 알고 있다는 것입니다. 다른 클래스의 메소드로 전달할 수 있으며 걱정하지 않아도됩니다. 전달되는 다른 메소드 또는 참조하는 다른 클래스는 중요하지 않습니다. (메모리 누수는 객체를 생성 한 클래스가 아니라 객체를 참조하는 클래스의 책임입니다.)

GC가 없으면 할당 된 메모리의 전체 수명주기를 추적해야합니다. 주소를 생성 한 서브 루틴에서 주소를 올리거나 내릴 때마다 해당 메모리에 대한 제어 불능 참조가 있습니다. 예전에는 한 개의 스레드만으로도 재귀와 운영 체제 (Windows NT)만으로도 할당 된 메모리에 대한 액세스를 제어 할 수 없었습니다. 모든 참조가 지워질 때까지 한동안 메모리 블록을 유지하기 위해 자체 할당 시스템에서 무료 방법 을 조작해야 했습니다. 유지 시간은 순수한 추측이지만 효과가있었습니다.

이것이 내가 아는 유일한 GC 혜택이지만, 그것 없이는 살 수 없었습니다. 나는 어떤 종류의 OOP도 그것 없이는 날지 않을 것이라고 생각합니다.


1
필자의 머리 꼭대기에서 델파이와 C ++는 GC가없는 OOP 언어로 상당히 성공했다. "제어 불능 참조"를 방지하기 위해 필요한 것은 약간의 훈련입니다. 단일 소유권 원칙을 이해하면 (내 답변 참조) 여기서 말하는 문제가 총 문제가되지 않습니다.
메이슨 휠러

@MasonWheeler : 소유자 객체가 해제 될 때가되면 소유 객체가 참조되는 모든 위치를 알아야합니다. 이 정보를 유지하고 참조를 제거하기 위해 사용하는 것은 나에게 많은 일인 것 같습니다. 나는 종종 참고 문헌을 아직 정리 할 수 ​​없다는 것을 알았습니다. 소유자를 삭제 된 것으로 표시 한 다음 안전하게 해방 될 수 있는지 정기적으로 확인해야합니다. 필자는 델파이를 사용한 적이 없지만 실행 효율성의 작은 희생을 위해 C # / Java는 C ++보다 개발 시간이 크게 향상되었습니다. (모든 GC로 인해,하지만 도움이되었다.)
RalphChapin

1

물리적 누출

GC가 해결하는 버그의 종류는 (적어도 외부 관찰자에게는) 자신의 언어, 라이브러리, 개념, 관용구 등을 잘 아는 프로그래머가하지 않는 종류의 것으로 보입니다. 그러나 나는 틀릴 수있다 : 수동 메모리 처리는 본질적으로 복잡합니까?

메모리 관리를 수동으로 만들고 가능한 한 발음하는 C 끝에서 왔으므로 극단적 인 비교를 위해 (C ++은 대부분 GC가없는 메모리 관리를 자동화합니다) GC와 비교할 때 "실제로 그렇지 않습니다"라고 말합니다. 누수발생 합니다. 초보자와 때로는 전문가조차도 free주어진 글을 쓰는 것을 잊어 버릴 수 있습니다 malloc. 확실히 일어난다.

그러나, valgrind누수 탐지 와 같은 도구 가있어 코드를 실행할 때 정확한 코드 줄까지 실수가 발생할 때 코드를 즉시 발견 할 수 있습니다. 그것이 CI에 통합되면, 그러한 실수를 합병하는 것은 거의 불가능하며,이를 수정하기위한 파이처럼 쉬워집니다. 따라서 합리적인 표준을 가진 모든 팀 / 프로세스에서 큰 문제는 아닙니다.

물론, free호출 이 실패한 테스트 레이더 하에서 실행되는 이국적인 실행 사례가있을 수 있습니다 . 아마도 손상된 파일과 같이 모호한 외부 입력 오류가 발생하면 시스템이 32 바이트를 유출시킬 수 있습니다. 나는 꽤 좋은 테스트 표준과 누수 탐지 도구에서도 확실히 일어날 수 있다고 생각하지만 거의 일어나지 않는 무언가에 대해 약간의 메모리를 누수하는 것은 그렇게 중요하지 않을 것입니다. GC가 막을 수없는 방식으로 일반적인 실행 경로에서도 대량의 리소스를 유출 할 수있는 훨씬 더 큰 문제가 있습니다.

객체의 수명이 어떤 형태의 지연 / 비동기 처리를 위해 아마도 다른 스레드에 의해 확장되어야 할 때 의사 형태의 GC (참조 횟수 계산)와 유사한 것이 없으면 어렵습니다.

매달려있는 포인터

더 많은 수동 메모리 관리 형식의 실제 문제는 나에게 누출되지 않습니다. C 또는 C ++로 작성된 네이티브 응용 프로그램이 실제로 누출되어 있다는 것을 알고 있습니까? 리눅스 커널이 새는가? MySQL? 크라이 엔진 3? 디지털 오디오 워크 스테이션 및 신시사이저? Java VM이 누출됩니까 (기본 코드로 구현 됨)? 포토샵?

우리가 둘러 볼 때 가장 누설이 많은 응용 프로그램은 GC 체계를 사용하여 작성된 경향이 있다고 생각합니다. 그러나 가비지 수집에 대한 슬램으로 사용되기 전에 네이티브 코드에는 메모리 누수와 전혀 관련이없는 중요한 문제가 있습니다.

나를위한 문제는 항상 안전이었습니다. 우리 free가 포인터를 통해 메모리 할 때에도 리소스에 대한 다른 포인터가 있으면 매달려있는 포인터가됩니다.

매달려있는 포인터의 포인트에 접근하려고 할 때 정의되지 않은 동작이 발생하지만 거의 항상 segfault / access 위반이 발생하여 즉시 즉각적인 충돌이 발생합니다.

위에 나열된 모든 기본 응용 프로그램에는 잠재적으로 모호한 경우가 하나 또는 두 개가있어 주로이 문제로 인해 충돌이 발생할 수 있으며 기본 코드로 작성된 대충 응용 프로그램이 상당히 크며 종종 충돌이 심합니다. 이 문제로 인해 대부분.

... GC 사용 여부에 관계없이 리소스 관리 가 어렵 기 때문 입니다. 실질적인 차이는 종종 자원 관리 오류로 이어지는 실수로 인해 누출 (GC) 또는 충돌 (GC 없음)입니다.

자원 관리 : 가비지 콜렉션

복잡한 자원 관리는 어쨌든 어려운 수동 프로세스입니다. GC는 여기서 아무것도 자동화 할 수 없습니다.

"Joe"라는이 개체가있는 예를 보자. Joe는 자신이 속한 여러 조직에서 참조합니다. 매월마다 그들은 신용 카드에서 회원 요금을 추출합니다.

여기에 이미지 설명을 입력하십시오

우리는 또한 조의 생애를 통제하기위한 언급을 하나 가지고 있습니다. 프로그래머로서 더 이상 Joe가 필요하지 않다고 가정 해 봅시다. 그는 우리를 괴롭히기 시작했으며 우리는 더 이상 그가 속한 사람들을 처리하는 데 시간을 낭비 할 필요가 없습니다. 그래서 우리는 생명선 참조를 제거하여 지구의 얼굴을 닦으려고 시도합니다.

여기에 이미지 설명을 입력하십시오

...하지만 가비지 수집을 사용하고 있습니다. Joe에 대한 모든 강력한 언급은 그를 계속 지켜줄 것입니다. 따라서 우리는 그가 속한 조직에서 그를 언급하지 않고 제거합니다.

여기에 이미지 설명을 입력하십시오

... 아무도 빼고, 그의 잡지 구독을 취소하는 것을 잊었다! 이제 Joe는 메모리에 남아 우리를 괴롭 히고 리소스를 사용하며 매거진은 매달 Joe의 멤버십을 계속 처리합니다.

이것은 가비지 수집 체계를 사용하여 작성된 많은 복잡한 프로그램이 누출되어 실행 시간이 길어질수록 더 많은 메모리를 사용하기 시작하고 더 많은 처리가 반복 될 수 있도록하는 주요 실수입니다 (반복 잡지 구독). 그들은 하나 이상의 해당 참조를 제거하는 것을 잊어 버렸으므로 가비지 수집기가 전체 프로그램이 종료 될 때까지 마술을 사용할 수 없습니다.

그러나 프로그램은 충돌하지 않습니다. 완벽하게 안전합니다. 메모리를 계속 끌어 올릴 것이며 Joe는 계속 남아있을 것입니다. 많은 응용 프로그램의 경우 문제가 발생할 때 더 많은 메모리 / 처리를 던지는 이러한 종류의 누수 동작은 특히 오늘날 컴퓨터의 메모리 및 처리 능력이 많은 경우 하드 충돌보다 훨씬 바람직 할 수 있습니다.

자원 관리 : 수동

이제 Joe와 수동 메모리 관리에 대한 포인터를 사용하는 대안을 고려해 보겠습니다.

여기에 이미지 설명을 입력하십시오

이 파란색 링크는 Joe의 수명을 관리하지 않습니다. 우리가 지구의 얼굴에서 그를 제거하려면 다음과 같이 수동으로 그를 파괴하도록 요청하십시오.

여기에 이미지 설명을 입력하십시오

이제는 일반적으로 모든 장소에 매달려있는 포인터를 남겨 둘 것이므로 Joe에 대한 포인터를 제거합시다.

여기에 이미지 설명을 입력하십시오

... 아, 우리는 똑같은 실수를 다시하고 Joe의 잡지 구독을 구독 취소하는 것을 잊었다!

지금을 제외하고 매달려있는 포인터가 있습니다. 잡지 구독이 Joe의 월별 요금을 처리하려고하면 전 세계가 폭발 할 것입니다.

개발자가 리소스에 대한 모든 포인터 / 참조를 수동으로 제거하는 것을 잊어 버렸던 이와 동일한 기본 리소스 잘못 관리 실수는 기본 응용 프로그램에서 많은 충돌을 일으킬 수 있습니다. 이 경우에는 종종 충돌이 발생하기 때문에 일반적으로 실행되는 시간이 길어질수록 메모리를 낭비하지 않습니다.

현실 세계

위의 예제는 엄청나게 간단한 다이어그램을 사용하고 있습니다. 실제 응용 프로그램은 전체 그래프를 덮기 위해 수천 개의 이미지가 꿰매어 있어야 할 수 있습니다. 장면 그래프에 저장된 수백 가지 유형의 리소스, 일부 관련 GPU 리소스, 다른 리소스와 연결된 가속기, 수백 개의 플러그인에 분산 된 관찰자 변화, 관찰자 ​​관찰자 관찰, 애니메이션과 동기화 된 오디오 등을 위해 장면에서 여러 엔티티 유형을 관찰합니다. 따라서 위에서 설명한 실수를 피하는 것이 쉬운 것처럼 보일 수 있지만 일반적으로 현실 세계에서이 간단한 곳은 거의 없습니다 수백만 줄의 코드에 걸친 복잡한 응용 프로그램을위한 프로덕션 코드베이스.

언젠가 누군가가 해당 코드베이스의 어딘가에서 리소스를 잘못 관리 할 가능성은 상당히 높으며 GC의 유무에 관계없이 확률은 동일합니다. 가장 큰 차이점은이 실수의 결과로 일어날 일이며,이 실수는이 실수를 발견하고 수정하는 속도에 잠재적으로 영향을 미칩니다.

충돌 대 누출

이제 어느 것이 더 나빠요? Joe가 신비하게 남아있는 즉각적인 충돌 또는 자동 메모리 누수?

대부분이 후자에 대답 할 수도 있지만이 소프트웨어는 몇 시간, 아마도 며칠 동안 실행되도록 설계되었으며 이러한 Joe와 Jane의 각 소프트웨어는 기가 바이트 단위로 소프트웨어의 메모리 사용량을 증가시킵니다. 미션 크리티컬 한 소프트웨어가 아니라 (크래시는 실제로 사용자를 죽이지 않음), 성능에 중요한 소프트웨어입니다.

이 경우 디버깅 할 때 즉시 나타나고 실수를 지적하는 하드 크래시는 실제로 테스트 절차의 레이더 아래에서 날 수있는 누수 소프트웨어보다 선호 될 수 있습니다.

반대로, 성능이 목표가 아닌 미션 크리티컬 한 소프트웨어라면 가능한 어떤 수단으로도 충돌하지 않으면 누출이 실제로 바람직 할 수 있습니다.

약한 참조

약한 참조로 알려진 GC 체계에는 이러한 아이디어가 혼합되어 있습니다. 약한 참조를 사용하면 이러한 모든 조직에 약한 참조 조를 지정할 수 있지만 강력한 참조 (조의 소유자 / 라이프 라인)가 사라질 때 그를 제거하지 못하게 할 수는 없습니다. 그럼에도 불구하고 Joe가 이러한 약한 참조를 더 이상 사용하지 않는시기를 감지하여 쉽게 재현 할 수없는 오류를 얻을 수 있다는 이점이 있습니다.

불행히도 약한 참조는 사용되어야 할만큼 거의 사용되지 않으므로 복잡한 C 응용 프로그램보다 복잡한 GC 응용 프로그램이 훨씬 덜 충돌하더라도 누출이 발생하기 쉽습니다.

어쨌든 GC로 인해 인생을 더 편하게 또는 어렵게 만드는지 여부는 소프트웨어가 누수를 피하는 것이 얼마나 중요한지, 그리고 이러한 종류의 복잡한 리소스 관리를 다루는 지 여부에 달려 있습니다.

필자의 경우 리소스가 수백 메가 바이트에서 기가 바이트에 이르는 성능에 중요한 필드에서 작업하며 위와 같은 실수로 인해 사용자가 언로드를 요청할 때 해당 메모리를 해제하지 않으면 실제로 충돌 보다 덜 바람직 할 수 있습니다 . 충돌은 쉽게 발견하고 재현 할 수 있으므로 사용자가 가장 좋아하지 않는 경우에도 프로그래머가 자주 사용하는 버그입니다. 이러한 충돌은 대부분 사용자에게 도달하기 전에 제정신 테스트 절차와 함께 나타납니다.

어쨌든, 이것들은 GC와 수동 메모리 관리의 차이점입니다. 즉각적인 질문에 대답하기 위해 수동 메모리 관리는 어렵지만 누출과는 거의 관련이 없으며 리소스 관리가 중요하지 않은 경우 GC 및 수동 메모리 관리 양식은 여전히 매우 어렵습니다 . GC는 아마도 프로그램이 잘 작동하는 것처럼 보이지만 점점 더 많은 리소스를 소비하는 경우 더 까다로운 행동을합니다. 수동 양식은 덜 까다 롭지 만 위에 표시된 것과 같은 실수로 충돌하여 큰 시간을 소비합니다.


-1

메모리를 다룰 때 C ++ 프로그래머가 직면 한 문제의 목록은 다음과 같습니다.

  1. 범위 할당 문제 는 스택 할당 메모리에서 발생합니다. 수명은 할당 된 함수 외부로 연장되지 않습니다.이 문제에 대한 세 가지 주요 솔루션이 있습니다. 힙 메모리 및 호출 스택에서 할당 포인트를 위로 이동하거나 객체 내부에서 할당 .
  2. Sizeof 문제 는 스택 할당 및 내부 오브젝트 및 일부 힙 할당 메모리에서 할당에 있습니다. 런타임시 메모리 블록의 크기를 변경할 수 없습니다. 솔루션은 힙 메모리 배열, 포인터 및 라이브러리 및 컨테이너입니다.
  3. 정의 순서 문제 는 내부 객체에서 할당 할 때입니다. 프로그램 내부의 클래스는 올바른 순서 여야합니다. 솔루션은 트리에 대한 종속성을 제한하고 클래스를 재정렬하며 정방향 선언, 포인터 및 힙 메모리를 사용하지 않고 정방향 선언을 사용합니다.
  4. 내부-외부 문제 는 객체 할당 메모리에 있습니다. 객체 내부의 메모리 액세스는 두 부분으로 나뉘며, 일부 메모리는 객체 내부에 있고 다른 메모리는 외부에 있습니다. 프로그래머는이 결정에 따라 컴포지션 또는 참조를 사용하도록 올바르게 선택해야합니다. 솔루션이 결정을 올바르게 수행하고 있거나 포인터 및 힙 메모리를 사용 중입니다.
  5. 재귀 객체 문제 는 객체 할당 메모리에 있습니다. 동일한 객체가 내부에 배치되면 객체의 크기가 무한 해지며 솔루션은 참조, 힙 메모리 및 포인터입니다.
  6. 소유권 추적 문제 는 힙 할당 메모리에 있으며, 힙 할당 메모리의 주소를 포함하는 포인터는 할당 지점에서 할당 해제 지점으로 전달되어야합니다. 솔루션은 스택 할당 메모리, 객체 할당 메모리, auto_ptr, shared_ptr, unique_ptr, stdlib 컨테이너입니다.
  7. 소유권 중복 문제 는 힙 할당 메모리에 있습니다. 할당 해제는 한 번만 수행 할 수 있습니다. 솔루션은 스택 할당 메모리, 객체 할당 메모리, auto_ptr, shared_ptr, unique_ptr, stdlib 컨테이너입니다.
  8. 널 포인터 문제점 이 힙 할당 메모리에 있습니다. 포인터가 널일 수 있으므로 런타임시 많은 조작이 충돌합니다. 솔루션은 스택 메모리, 객체 할당 메모리 및 힙 영역 및 참조에 대한 신중한 분석입니다.
  9. 메모리 누수 문제 가 힙 할당 메모리에 있음 : 할당 된 모든 메모리 블록에 대해 삭제 호출을 잊어 버렸습니다. 솔루션은 valgrind와 같은 도구입니다.
  10. 스택 오버플로 문제 는 스택 메모리를 사용하는 재귀 함수 호출에 대한 것입니다. 일반적으로 스택 크기는 재귀 알고리즘의 경우를 제외하고 컴파일 시간에 완전히 결정됩니다. OS의 스택 크기를 잘못 정의하면 필요한 스택 공간 크기를 측정 할 방법이 없기 때문에 종종이 문제가 발생합니다.

보시다시피 힙 메모리는 기존의 많은 문제를 해결하지만 추가 복잡성을 유발합니다. GC는 이러한 복잡성의 일부를 처리하도록 설계되었습니다. (일부 문제 이름이 이러한 문제의 올바른 이름이 아닌 경우 죄송합니다. 때로는 올바른 이름을 찾기가 어렵습니다.)


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