C ++에 가비지 수집기가없는 이유는 무엇입니까? 알고 있기

가비지 수집의 장점으로 인해이 질문을하지 않습니다. 내가 묻는 주된 이유는 Bjarne Stroustrup이 C ++이 특정 시점에 가비지 수집기를 가질 것이라고 말했다는 것을 알고 있기 때문입니다.

그렇게 말하면서 왜 추가되지 않았습니까? C ++에 대한 가비지 수집기가 이미 있습니다. 이것이 “한 것보다 더 쉬운 말”유형의 것 중 하나입니까? 아니면 추가되지 않은 다른 이유가 있습니까 (C ++ 11에서는 추가되지 않음)?

교차 링크 :

명확히하기 위해 C ++에 가비지 수집기가 처음으로 생성되지 않은 이유를 이해합니다. 수집기를 추가 할 수없는 이유가 궁금합니다.



답변

암시 적 가비지 콜렉션이 추가되었을 수는 있지만 그저 잘리지 않았습니다. 아마도 구현의 합병증뿐만 아니라 사람들이 일반적인 합의에 빨리 도달 할 수 없기 때문일 것입니다.

Bjarne Stroustrup 자신의 인용문 :

선택적으로 활성화 할 수있는 가비지 수집기가 C ++ 0x의 일부가되기를 희망했지만 그러한 수집기가 다른 언어와 통합되는 방법에 대한 자세한 사양으로 수행해야 할 기술적 인 문제가 충분했습니다. 제공되는 경우 본질적으로 모든 C ++ 0x 기능의 경우와 마찬가지로 실험적인 구현이 존재합니다.

여기서 주제에 대한 좋은 토론이 있습니다 .

일반 개요 :

C ++은 매우 강력하며 거의 모든 작업을 수행 할 수 있습니다. 이러한 이유로 성능에 영향을 줄 수있는 많은 것들을 자동으로 푸시하지는 않습니다. 가비지 수집은 스마트 포인터 (참조 카운트로 포인터를 감싸는 객체로 참조 카운트가 0에 도달하면 자동으로 삭제됨)를 사용하여 쉽게 구현할 수 있습니다.

C ++은 가비지 콜렉션이없는 경쟁자를 염두에두고 작성되었습니다. 효율성은 C ++이 C 및 기타와 비교하여 비판을 피해야하는 주요 관심사였습니다.

가비지 수집에는 두 가지 유형이 있습니다 …

명시 적 가비지 콜렉션 :

C ++ 0x는 shared_ptr로 작성된 포인터를 통해 가비지 콜렉션을 갖습니다.

당신이 그것을 원한다면 당신은 그것을 사용할 수 있습니다, 당신이 그것을 원하지 않는다면 당신은 그것을 사용하도록 강요되지 않습니다.

C ++ 0x를 기다리지 않으려면 현재 boost : shared_ptr을 사용할 수 있습니다.

암시 적 가비지 콜렉션 :

그러나 투명한 가비지 콜렉션이 없습니다. 향후 C ++ 사양의 초점이 될 것입니다.

Tr1에 암시 적 가비지 콜렉션이없는 이유는 무엇입니까?

이전 인터뷰에서 Bjarne Stroustrup은 tr1이 자신이 원하는 것만 큼을 가지고 있지 않다고 밝혔다.


답변

토론에 추가하십시오.

가비지 수집에는 알려진 문제가 있으며이를 이해하면 C ++에없는 이유를 이해하는 데 도움이됩니다.

1. 성능?

첫 번째 불만은 종종 성과에 관한 것이지만 대부분의 사람들은 자신이 말하는 것을 실제로 깨닫지 못합니다. Martin Beckett문제에서 알 수 있듯이 성능 자체가 아니라 성능의 예측 가능성입니다.

현재 널리 배포되는 2 가지 GC 제품군이 있습니다.

  • 마크 앤 스윕 종류
  • 참조 카운팅 종류

Mark And Sweep(전체 성능에 미치는 영향) 빠르다 그러나 그것은 “세상을 동결”증후군을 앓고 : 즉, GC가 정리를했다 때까지의 GC의 킥은, 다른 모든 정지시. 몇 밀리 초 안에 응답하는 서버를 구축하려는 경우 일부 트랜잭션은 예상대로 작동하지 않습니다. 🙂

문제 Reference Counting는 다릅니다. 참조 카운트는 원자 수를 가져야하기 때문에 특히 멀티 스레딩 환경에서 오버 헤드를 추가합니다. 또한 참조주기 문제가 있으므로 이러한주기를 감지하고 제거하기위한 영리한 알고리즘이 필요합니다 (일반적으로 “세계 동결”에 의해 구현되지만 빈도는 낮음). 일반적으로 오늘날 현재이 유형은 (일반적으로 더 반응 적이거나 오히려 얼어 붙지 않지만)보다 느립니다 Mark And Sweep.

에펠 구현 자들이 “세계 정지 (Freeze The World)”측면 없이도 Reference Counting비슷한 글로벌 성능을 가진 가비지 콜렉터 를 구현하려는 논문을 보았습니다 Mark And Sweep. GC에는 별도의 스레드가 필요했습니다 (일반). 알고리즘은 약간 무서웠지만 (종료) 개념은 한 번에 하나씩 개념을 소개하고 “간단한”버전에서 본격적인 알고리즘으로 알고리즘의 진화를 보여주는 데 도움이되었습니다. PDF 파일에 손을 다시 넣을 수있는 경우에만 권장되는 내용 …

2. 자원 획득이 초기화 (RAII)

C++개체 내에서 리소스의 소유권을 래핑하여 제대로 해제되도록하는 것은 일반적인 관용구입니다 . 가비지 수집이 없기 때문에 주로 메모리에 사용되지만 그럼에도 불구하고 많은 다른 상황에서도 유용합니다.

  • 잠금 (멀티 스레드, 파일 핸들 등)
  • 연결 (데이터베이스, 다른 서버에 대한 …)

아이디어는 객체의 수명을 올바르게 제어하는 ​​것입니다.

  • 필요한만큼 살아 있어야합니다
  • 당신이 그것으로 끝나면 죽여야한다

GC의 문제는 그것이 전자를 돕고 궁극적으로 나중에 그것을 보장한다면 …이 “궁극적 인”것으로는 충분하지 않다는 것입니다. 잠금을 해제하면 더 이상 통화를 차단하지 않도록 잠금 해제를 원합니다!

GC를 사용하는 언어에는 두 가지 해결 방법이 있습니다.

  • 스택 할당이 충분할 때 GC를 사용하지 마십시오. 일반적으로 성능 문제에 대한 것이지만 범위가 수명을 정의하므로 실제로 도움이됩니다.
  • usingconstruct …하지만 C ++ RAII는 암시 적이지만 사용자가 실수로 ( using키워드 를 생략하여 ) 오류를 만들 수 없도록 명시 적 (약한) RAII입니다.

3. 스마트 포인터

스마트 포인터는 종종 메모리를 처리하기 위해 은색 총알로 나타납니다 C++. 스마트 포인터가 있으므로 GC가 필요하지 않은 경우가 종종 있습니다.

하나 더 잘못 될 수 없습니다.

스마트 포인터는 다음 auto_ptr과 같은 unique_ptr이점을 제공합니다. RAII 개념을 사용하면 실제로 매우 유용합니다. 그것들은 매우 간단하여 스스로 쉽게 작성할 수 있습니다.

그러나 소유권을 공유해야하는 경우 더 어려워집니다. 여러 스레드간에 공유 할 수 있으며 개수 처리에 약간의 미묘한 문제가 있습니다. 그러므로 자연스럽게쪽으로갑니다 shared_ptr.

대단합니다. 부스트는 결국 은색 총알이 아닙니다. 사실, 주요 문제 shared_ptr는 구현 된 GC를 에뮬레이트 Reference Counting하지만 직접 사이클 감지를 구현해야한다는 것입니다 … Urg

물론이 문제가 weak_ptr있지만 불행히도 shared_ptr그주기 때문에 사용에도 불구하고 이미 메모리 누수를 보았습니다 … 멀티 스레드 환경에 있으면 감지하기가 매우 어렵습니다!

4. 해결책은 무엇입니까?

은 총알은 없지만 항상 그렇듯이 실현 가능합니다. GC가 없으면 소유권에 대해 명확해야합니다.

  • 가능하면 한 번에 한 명의 소유자를 선호합니다
  • 그렇지 않은 경우 클래스 다이어그램에 소유권과 관련된주기가 없는지 확인하고 weak_ptr

실제로 GC를 갖는 것이 좋을 것입니다 … 그러나 사소한 문제는 아닙니다. 그리고 그 동안 우리는 소매를 굴려야합니다.


답변

어떤 타입? 내장형 세탁기 컨트롤러, 휴대폰, 워크 스테이션 또는 슈퍼 컴퓨터에 최적화해야합니까?
GUI 응답 성 또는 서버 로딩을 우선시해야합니까?
많은 메모리 나 많은 CPU를 사용해야합니까?

C / c ++는 너무 많은 다른 환경에서 사용됩니다. 부스트 스마트 포인터와 같은 것이 대부분의 사용자에게 충분하다고 생각합니다.

편집-자동 가비지 수집기는 성능 문제가 아니기 때문에 (항상 더 많은 서버를 구입할 수 있음) 이는 예측 가능한 성능의 문제입니다.
GC가 언제 시작 될지 모르는 것은 마치 수면제 항공사 조종사를 고용하는 것과 같습니다. 대부분의 시간은 훌륭합니다. 그러나 실제로 응답 성이 필요한 경우!


답변

C ++에 가비지 수집 기능이 내장되어 있지 않은 가장 큰 이유 중 하나는 가비지 수집기가 소멸자와 훌륭하게 작동하는 것이 실제로 매우 어렵다는 것입니다. 내가 아는 한 아직 아무도 그것을 완전히 해결하는 방법을 모른다. 처리해야 할 문제가 많이 있습니다.

  • 결정적 수명의 객체 (참조 횟수는 당신에게 제공하지만 GC는 그렇지 않습니다. 그렇게 큰 것은 아니지만).
  • 객체가 가비지 수집 될 때 소멸자가 발생하면 어떻게됩니까? 이를 전달할 수있는 catch 블록이 없으므로 대부분의 언어는이 예외를 무시하지만 C ++에 적합한 솔루션은 아닙니다.
  • 활성화 / 비활성화하는 방법은 무엇입니까? 당연히 컴파일 타임 결정 일지 모르지만 GC 용으로 작성된 코드 대 NOT GC 용으로 작성된 코드는 매우 다르며 호환되지 않을 것입니다. 이것을 어떻게 조정합니까?

이들은 직면 한 몇 가지 문제에 지나지 않습니다.


답변

이것은 오래 되었지만 질문 여전히 아무도 언급하지 않은 한 가지 문제가 있습니다. 가비지 수집은 거의 지정할 수 없습니다.

특히 C ++ 표준은 구현에서 해당 동작을 달성하는 방법이 아니라 외부에서 관찰 가능한 동작 측면에서 언어를 지정하는 데 매우 신중합니다. 가비지 컬렉션의 경우, 그러나,이 없는 사실상 외부에서 관찰 할 수있는 행동.

가비지 수집 의 일반적인 아이디어 는 메모리 할당이 성공할 수 있도록 합리적으로 시도해야한다는 것입니다. 불행히도, 가비지 수집기가 작동하더라도 메모리 할당이 성공하도록 보장하는 것은 본질적으로 불가능합니다. 이것은 수집주기 동안 객체를 메모리로 이동시키는 복사 수집기 (또는 이와 유사한 것)를 사용하는 것이 불가능할 수 있기 때문에 어떤 경우에도 어느 정도는 사실이지만 특히 C ++의 경우 특히 그렇습니다.

객체를 이동할 수없는 경우 할당을 수행 할 연속적인 단일 메모리 공간을 만들 수 없습니다. 즉, 힙 (또는 비어있는 저장소 또는 원하는 것을 호출하는 것)이 가능할 것입니다. 시간이 지남에 따라 조각화됩니다. 결과적으로 요청 된 양보다 많은 메모리 여유 공간이 있어도 할당이 성공하지 못하게 할 수 있습니다.

할 수 있지만 일부 반복적으로 할당 정확히 같은 패턴을 반복하는 경우 있음 (본질적으로)라고 보장하고, 처음으로 성공, 그것은 제공, 이후의 반복에 성공 계속 그 할당 된 메모리 반복 사이에 액세스 할 수 없게되었습니다. 그것은 본질적으로 쓸모없는 약한 보증이지만 그것을 강화하려는 합리적인 희망을 볼 수는 없습니다.

그럼에도 불구하고 C ++에 제안 된 것보다 강력합니다. 그만큼이전 제안 [경고 : PDF] (감소되었다) 모든에서 보증 아무것도했다. 28 페이지의 제안서에서, 외부에서 관찰 가능한 행동을 방해하는 것은 단일 (비 규범 적) 메모입니다.

[참고 : 가비지 수집 프로그램의 경우 고품질 호스팅 구현은 회수 할 수없는 메모리 양을 최대화해야합니다. — 끝 노트]

적어도 나를 위해, 이것은 투자 수익에 대한 심각한 의문 됩니다. 우리는 기존 코드를 깨뜨릴 것입니다 (아무도 확실하지는 않지만 확실히 조금), 구현에 대한 새로운 요구 사항과 코드에 대한 새로운 제한을 적용하고, 그 대가로 얻는 것은 전혀 아무것도 아닐까요?

기껏해야 Java를 이용한 테스트를 기반으로하는 프로그램은 해야 동일한 속도로 실행하려면 약 6 배의 메모리가 필요할 것입니다. 더구나 가비지 수집은 처음부터 Java의 일부였습니다 .C ++는 가비지 수집기에 더 많은 제한을 두어 비용 / 혜택 비율이 훨씬 더 나빠질 것입니다 (제안이 보장 한 것을 넘어서고 가정 할지라도) 일부 혜택).

나는 상황을 수학적으로 요약했다 : 이것은 복잡한 상황이다. 모든 수학자가 알고 있듯이 복소수에는 실수와 허수의 두 부분이 있습니다. 우리가 여기있는 것은 실제 비용이지만 (적어도 대부분은) 상상적인 혜택 인 것으로 보입니다.


답변

자동 가비지 수집을 원한다면 C ++에 적합한 상용 및 공개 도메인 가비지 수집기가 있습니다. 가비지 수집이 적합한 응용 프로그램의 경우 C ++은 다른 가비지 수집 언어와 비교하여 성능이 우수한 우수한 가비지 수집 언어입니다. C ++ 의 자동 가비지 콜렉션에 대한 설명은 C ++ 프로그래밍 언어 (제 4 판) 를 참조하십시오 . Hans-J. C 및 C ++ 가비지 수집을위한 Boehm 사이트 ( archive )

또한 C ++는 가비지 수집기없이 메모리 관리를 안전하고 암시 적 으로 허용하는 프로그래밍 기술을 지원합니다 . 가비지 수집은 마지막 선택이며 리소스 관리를위한 불완전한 처리 방법이라고 생각합니다. 그렇다고 결코 유용하지 않다는 의미는 아니며, 많은 상황에서 더 나은 접근 방법이 있다는 것입니다.

출처: http://www.stroustrup.com/bs_faq.html#garbage-collection

이 나던 이유에 관해서는 내가 정확히 기억한다면 GC가 전에이 발명되었다, 내장 , 나는 언어가 여러 가지 이유 (C와 IE 이전 버전과의 호환성 (을))에 대한 GC를 가질 수도 믿지 않는다

도움이 되었기를 바랍니다.


답변

Stroustrup은 2013 Going Native 컨퍼런스에서 이에 대해 좋은 의견을 남겼습니다.

이 비디오 에서 약 25m50으로 건너 뛰십시오. . (실제로 전체 비디오를 보는 것이 좋지만 가비지 수집에 대해서는 건너 뜁니다.)

객체를 사용하는 것을 피하고 (명시 적으로) 사용하지 않고 객체와 값을 직접 처리하는 것이 쉽고 안전하고 예측 가능하며 읽기 쉽고 가르치기 쉬운 훌륭한 언어를 사용하는 경우 힙을 사용하면 가비지 수집 조차 원하지 않습니다 .

최신 C ++ 및 C ++ 11에 포함 된 기능을 사용하면 제한된 환경을 제외하고 더 이상 가비지 수집이 바람직하지 않습니다. 사실, 훌륭한 가비지 수집기가 주요 C ++ 컴파일러 중 하나에 내장되어 있어도 자주 사용되지는 않을 것이라고 생각합니다. GC를 피하는 것이 더 어렵지 는 않지만 더 쉬울 것 입니다.

그는이 예를 보여줍니다.

void f(int n, int x) {
    Gadget *p = new Gadget{n};
    if(x<100) throw SomeException{};
    if(x<200) return;
    delete p;
}

C ++에서는 안전하지 않습니다. 그러나 Java에서도 안전하지 않습니다! C ++에서 함수가 일찍 반환되면는 delete호출되지 않습니다. 그러나 Java에서와 같이 전체 가비지 콜렉션이있는 경우, 오브젝트가 “향후 어느 시점에”파괴 될 것이라는 제안 만받을뿐입니다 ( 업데이트 : 이보다 더 나쁩니다. 하지파이널 라이저를 호출 할 것을 약속합니다. 가젯에 열린 파일 핸들, 데이터베이스에 대한 연결 또는 나중에 데이터베이스에 쓰기 위해 버퍼링 한 데이터가있는 경우에는 충분하지 않습니다. 이러한 리소스를 최대한 빨리 확보하기 위해 가제트가 완료 되 자마자 파괴되기를 바랍니다. 더 이상 필요하지 않은 수천 개의 데이터베이스 연결로 데이터베이스 서버가 어려움을 겪고 싶지는 않습니다.

그래서 해결책은 무엇입니까? 몇 가지 접근 방식이 있습니다. 대부분의 객체에 사용할 명백한 접근 방식은 다음과 같습니다.

void f(int n, int x) {
    Gadget p = {n};  // Just leave it on the stack (where it belongs!)
    if(x<100) throw SomeException{};
    if(x<200) return;
}

입력하는 데 걸리는 문자 수가 줄어 듭니다. new방해 가되지 않습니다 . Gadget두 번 입력하지 않아도 됩니다. 함수가 끝나면 객체가 파괴됩니다. 이것이 당신이 원하는 것이라면, 이것은 매우 직관적입니다. Gadget같은 동작합니다이야 intdouble. 예측 가능하고 읽기 쉽고 가르치기 쉬운. 모든 것이 ‘가치’입니다. 때로는 큰 가치가 있지만 포인터 (또는 참조)로 얻는 ‘먼 거리에서’동작하지 않기 때문에 값을 가르치기가 더 쉽습니다.

대부분의 객체는 객체를 만든 함수에서만 사용되며 자식 함수에 대한 입력으로 전달 될 수 있습니다. 프로그래머는 객체를 반환하거나 광범위하게 분리 된 소프트웨어 부분에서 객체를 공유 할 때 ‘메모리 관리’에 대해 생각할 필요가 없습니다.

범위와 수명이 중요합니다. 대부분의 경우 수명이 범위와 동일하면 더 쉽습니다. 이해하기 쉽고 가르치기가 더 쉽습니다. 다른 수명을 원할 때, shared_ptr예를 들어 이것을 사용하여 코드를 읽는 것이 분명해야합니다 . 또는 이동 의미를 활용하거나 값으로 (큰) 객체를 반환하거나 unique_ptr.

이것은 효율성 문제처럼 보일 수 있습니다. 가제트를 반환하려면 어떻게해야 foo()합니까? C ++ 11의 이동 의미는 큰 객체를 더 쉽게 반환 할 수있게합니다. 쓰기 Gadget foo() { ... }만하면 작동하고 빠르게 작동합니다. 당신은 &&자신 을 엉망으로 만들 필요가 없으며 , 가치로 물건을 반환하면 언어는 종종 필요한 최적화를 수행 할 수 있습니다. (C ++ 03 이전에도 컴파일러는 불필요한 복사를 피하는 데 크게 도움이되었습니다.)

Stroustrup이 비디오의 다른 부분에서 이야기 한 것처럼 (작은 글씨로) : “컴퓨터 과학자 만이 대상을 복사 한 다음 원본을 파괴해야합니다. (컴퓨터 과학자가 아닌) 기대합니다. “

객체의 사본 하나만 필요하다는 것을 보장 할 수 있으면 객체의 수명을 이해하는 것이 훨씬 쉽습니다. 원하는 평생 정책을 선택할 수 있으며 원하는 경우 가비지 수집이 있습니다. 그러나 다른 접근 방식의 이점을 이해하면 가비지 수집이 환경 설정 목록의 맨 아래에 있습니다.

그래도 문제가 해결되지 않으면을 (를) 사용 unique_ptr하거나 실패 할 수 있습니다 shared_ptr. 잘 작성된 C ++ 11은 메모리 관리와 관련하여 다른 많은 언어보다 짧고 읽기 쉽고 가르치기 쉽습니다.