C ++의 스택, 정적 및 힙 작성할 수 있습니까? 다른 언어에는

검색했지만이 세 가지 개념을 잘 이해하지 못했습니다. 힙에서 동적 할당을 언제 사용해야합니까? 그리고 실제 장점은 무엇입니까? 정적 및 스택의 문제점은 무엇입니까? 힙에 변수를 할당하지 않고 전체 응용 프로그램을 작성할 수 있습니까?

다른 언어에는 “가비지 수집기”가 포함되어 있으므로 메모리에 대해 걱정할 필요가 없습니다. 가비지 수집기는 무엇을합니까?

이 가비지 수집기를 사용하여 수행 할 수없는 메모리를 직접 조작 할 수 있습니까?

누군가가이 선언으로 저에게 말했습니다 :

int * asafe=new int;

“포인터에 대한 포인터”가 있습니다. 무슨 뜻인가요? 다음과 다릅니다.

asafe=new int;

?



답변

비슷한 질문 이 있었지만 정적에 대해서는 묻지 않았습니다.

정적, 힙 및 스택 메모리의 요약 :

  • 정적 변수는 전역 적으로 액세스 할 수없는 경우에도 기본적으로 전역 변수입니다. 일반적으로 실행 파일 자체에 주소가 있습니다. 전체 프로그램에 대해 하나의 사본 만 있습니다. 함수 호출 (또는 클래스) (및 스레드 수)에 몇 번이나 상관없이 변수는 동일한 메모리 위치를 참조합니다.

  • 힙은 동적으로 사용할 수있는 메모리입니다. 객체에 4kb를 원하면 동적 할당자는 힙에서 사용 가능한 공간 목록을 살펴보고 4kb 청크를 선택하여 사용자에게 제공합니다. 일반적으로 동적 메모리 할당 자 (malloc, new 등)는 메모리 끝에서 시작하여 뒤로 작동합니다.

  • 스택이 어떻게 늘어나고 줄어드는 지 설명하는 것은이 답변의 범위를 벗어나지 만, 항상 끝에서만 추가하고 제거한다고 말하면 충분합니다. 스택은 일반적으로 시작하여 더 낮은 주소로 증가합니다. 스택이 중간 어딘가에서 동적 할당자를 만나면 메모리가 부족합니다 (물리적 메모리와 가상 메모리 및 조각화 참조). 다중 스레드에는 다중 스택이 필요합니다 (프로세스는 일반적으로 스택의 최소 크기를 예약합니다).

각각을 사용하고 싶을 때 :

  • 스태틱 / 글로벌은 항상 필요하고 할당 해제를 원하지 않는 메모리에 유용합니다. (이러한 방식으로, 임베디드 환경은 정적 메모리 만있는 것으로 생각할 수 있습니다. 스택 및 힙은 세 번째 메모리 유형 (프로그램 코드)이 공유하는 알려진 주소 공간의 일부입니다. 프로그램 코드는 종종 동적 할당을 수행합니다. 링크 된리스트와 같은 것들이 필요할 때 정적 메모리 그러나, 정적 메모리 자체 (버퍼) 자체는 “할당”되지 않고 오히려 다른 목적은이 목적을 위해 버퍼가 보유한 메모리에서 할당됩니다. 콘솔 게임은 내장 된 동적 메모리 메커니즘을 자주 배제하여 모든 할당에 대해 사전 설정된 크기의 버퍼를 사용하여 할당 프로세스를 엄격하게 제어합니다.)

  • 스택 변수는 함수가 범위 내 (스택 어딘가)에있는 한 변수가 유지되기를 원할 때 유용합니다. 스택은 코드가있는 코드에 필요하지만 해당 코드 외부에서는 필요하지 않은 변수에 좋습니다. 또한 파일과 같은 리소스에 액세스 할 때 유용하며 해당 코드를 떠날 때 리소스가 자동으로 사라지기를 원합니다.

  • 힙 할당 (동적 할당 메모리)은 위의 것보다 더 유연하게하고 싶을 때 유용합니다. 종종 이벤트에 응답하기 위해 함수가 호출됩니다 (사용자가 “상자 만들기”버튼을 클릭 함). 올바른 응답을 위해서는 함수가 종료 된 후 오래 지속되어야하는 새 개체 (새 Box 개체)를 할당해야하므로 스택에있을 수 없습니다. 그러나 프로그램을 시작할 때 얼마나 많은 상자를 원하는지 알 수 없으므로 정적 일 수 없습니다.

가비지 콜렉션

최근 가비지 콜렉터가 얼마나 훌륭한 지에 대해 많이 들었으므로 약간의 반대 의견이 도움이 될 것입니다.

가비지 콜렉션은 성능이 큰 문제가 아닐 때 훌륭한 메커니즘입니다. GC가 점점 더 정교 해지고 있다고 들었지만 사실 (사용 사례에 따라) 성능 저하를 감수해야 할 수도 있습니다. 게 으르더라도 여전히 제대로 작동하지 않을 수 있습니다. 가비지 콜렉터는 최상의 경우 참조가 더 이상 없다는 것을 알게되면 메모리가 사라진다는 것을 알게됩니다 ( 참조 횟수 계산 참조)). 그러나 자신을 참조하는 오브젝트가있는 경우 (아마도 다른 오브젝트를 참조하여 참조하는 경우) 참조 카운트만으로는 메모리를 삭제할 수 있음을 나타내지 않습니다. 이 경우 GC는 전체 참조 수프를보고 자신 만 참조하는 섬이 있는지 확인해야합니다. 오프 핸드, 나는 O (n ^ 2) 연산이라고 생각하지만, 그것이 무엇이든간에 성능에 관심이 있다면 나빠질 수 있습니다. (편집 : Martin B 는 합리적으로 효율적인 알고리즘의 경우 O (n)임을 지적 합니다. 성능에 관심이 있고 가비지 수집없이 일정한 시간에 할당을 취소 할 수 있으면 여전히 O (n)입니다.)

개인적으로 사람들이 C ++에 가비지 수집이 없다고 말하면 내 마음에 C ++의 기능으로 태그가 지정되지만 소수에 해당합니다. 아마도 사람들이 C와 C ++에서의 프로그래밍에 대해 배우기 가장 어려운 것은 포인터와 동적 메모리 할당을 올바르게 처리하는 방법 일 것입니다. 파이썬과 같은 다른 언어는 GC가 없으면 끔찍할 것이므로 언어에서 원하는 것에 달려 있다고 생각합니다. 신뢰할 수있는 성능을 원한다면 가비지 수집이없는 C ++이 Fortran의 유일한 측면입니다. 사용하기 쉽고 훈련 휠을 원한다면 ( “적절한”메모리 관리를 배우지 않고도 충돌을 피하기 위해) GC로 무언가를 선택하십시오. 메모리를 잘 관리하는 방법을 알고 있더라도 다른 코드를 최적화하는 데 시간을 절약 할 수 있습니다. 실제로 더 이상 성능 저하가 발생하지 않지만 신뢰할 수있는 성능 (및 커버 아래에서 진행 상황을 정확하게 알 수있는 기능)이 정말로 필요한 경우 C ++을 고수합니다. 내가 들어 본 모든 주요 게임 엔진이 C ++ (C 또는 어셈블리가 아닌 경우)에있는 이유가 있습니다. Python 등은 스크립팅에는 적합하지만 주요 게임 엔진에는 적합하지 않습니다.


답변

다음은 물론 모든 것이 정확하지는 않습니다. 읽을 때 소금 한알과 함께 가져 가십시오. 🙂

글쎄, 당신이 말하는 세 가지 것은 자동, 정적 및 동적 저장 기간 이며, 이것은 객체의 수명과 수명이 시작되는 시간 과 관련이 있습니다.


자동 저장 기간

당신은 자동 저장 기간 사용 짧은 수명작은 에만 필요 데이터를 로컬로 일부 블록 내를 :

if(some condition) {
    int a[3]; // array a has automatic storage duration
    fill_it(a);
    print_it(a);
}

수명은 블록을 종료하자마자 끝나고 객체가 정의 되 자마자 시작됩니다. 이들은 가장 간단한 종류의 스토리지 기간이며 특히 동적 스토리지 기간보다 훨씬 빠릅니다.


정적 저장 기간

사용 가능한 변수 (네임 스페이스 범위) 및 해당 범위의 종료 (로컬 범위)를 통해 수명을 연장해야하는 로컬 변수에 대해 모든 코드에서 항상 액세스 할 수있는 빈 변수에 대해 정적 저장 기간을 사용합니다. 클래스 (클래스 범위)의 모든 객체가 공유해야하는 멤버 변수 수명은 범위에 따라 다릅니다. 네임 스페이스 범위로컬 범위클래스 범위를 가질 수 있습니다 . 둘 다에 대한 진실은, 일단 그들의 삶이 시작되면 평생 은 프로그램의 끝에서 끝납니다 . 다음은 두 가지 예입니다.

// static storage duration. in global namespace scope
string globalA;
int main() {
    foo();
    foo();
}

void foo() {
    // static storage duration. in local scope
    static string localA;
    localA += "ab"
    cout << localA;
}

이 프로그램의 인쇄는 ababab, 때문에 localA그 블록의 종료에 파괴되지 않는다. 제어 범위가 정의에 도달하면 로컬 범위를 가진 개체의 수명이 시작된다고 말할 수 있습니다 . 의 경우 localA함수 본문이 입력 될 때 발생합니다. 네임 스페이스 범위에있는 개체의 경우 수명은 프로그램 시작시 시작 됩니다. 클래스 범위의 정적 객체에 대해서도 마찬가지입니다.

class A {
    static string classScopeA;
};

string A::classScopeA;

A a, b; &a.classScopeA == &b.classScopeA == &A::classScopeA;

보시다시피, classScopeA클래스의 특정 객체가 아니라 클래스 자체에 바인딩됩니다. 위의 세 이름 모두의 주소는 동일하며 모두 동일한 개체를 나타냅니다. 정적 객체가 언제 어떻게 초기화되는지에 대한 특별한 규칙이 있지만 지금은 걱정하지 마십시오. 이는 정적 초기화 순서 fiasco 라는 용어를 의미합니다 .


동적 저장 기간

마지막 저장 기간은 동적입니다. 다른 섬에 개체를 배치하고 해당 참조 주위에 포인터를 놓으려는 경우 사용합니다. 객체가 거나 런타임 에만 알려진 크기의 배열을 만들려 는 경우에도 사용합니다 . 이러한 유연성으로 인해 동적 스토리지 기간을 갖는 객체는 복잡하고 관리가 느립니다. 해당 동적 지속 시간을 가진 객체는 적절한 연산자 호출이 발생할 때 수명을 시작 합니다.

int main() {
    // the object that s points to has dynamic storage 
    // duration
    string *s = new string;
    // pass a pointer pointing to the object around. 
    // the object itself isn't touched
    foo(s);
    delete s;
}

void foo(string *s) {
    cout << s->size();
}

수명은 delete 를 호출 할 때만 종료됩니다 . 잊어 버린 경우 해당 개체의 수명이 끝나지 않습니다. 그리고 사용자 선언 생성자를 정의하는 클래스 객체에는 소멸자가 호출되지 않습니다. 동적 저장 시간을 갖는 객체는 수명 및 관련 메모리 리소스를 수동으로 처리해야합니다. 라이브러리는 쉽게 사용할 수 있도록 존재합니다. 스마트 포인터를 사용하여 특정 객체에 대한 명시 적 가비지 수집 을 설정할 수 있습니다.

int main() {
    shared_ptr<string> s(new string);
    foo(s);
}

void foo(shared_ptr<string> s) {
    cout << s->size();
}

객체를 참조하는 마지막 포인터가 범위를 벗어나면 delete 호출에 신경 쓸 필요가 없습니다. 공유 ptr이 자동으로 처리합니다. 공유 ptr 자체에는 자동 저장 기간이 있습니다. 그래서 수명이 자동으로 관리되어, 그것은 소멸자에서 동적 객체에 지적을 삭제할지 여부를 확인 할 수 있도록. shared_ptr 참조는 부스트 문서를 참조하십시오. http://www.boost.org/doc/libs/1_37_0/libs/smart_ptr/shared_ptr.htm


답변

“짧은 대답”과 같이 정교하게 언급되었습니다.

  • 정적 변수 (클래스)
    수명 = 프로그램 런타임 (1)
    가시성 = 액세스 수정 자에 의해 결정 (개인 / 보호 / 공용)

  • 정적 변수 (전역 범위)
    수명 = 프로그램 런타임 (1)
    가시성 = 인스턴스화되는 컴파일 단위 (2)

  • 힙 변수
    수명 = 사용자에 의해 정의 됨 (새로 삭제)
    가시성 = 사용자에 의해 정의 됨 (포인터를 할당 한 대상)

  • 스택 변수
    가시성 = 선언에서 범위가 종료 될 때까지
    수명 = 선언에서 선언 범위가 끝날 때까지


(1) 더 정확하게 : 초기화에서 컴파일 단위의 초기화 해제 (예 : C / C ++ 파일)까지. 컴파일 단위의 초기화 순서는 표준에 의해 정의되지 않습니다.

(2)주의 : 헤더에서 정적 변수를 인스턴스화하면 각 컴파일 단위가 자체 사본을 가져옵니다.


답변

나는 pedants 중 하나가 곧 더 나은 답변을 얻을 것이라고 확신하지만 주요 차이점은 속도와 크기입니다.

스택

할당 속도가 훨씬 빠릅니다. 스택 프레임을 설정할 때 할당되므로 본질적으로 자유 로워지기 때문에 O (1)에서 수행됩니다. 단점은 스택 공간이 부족하면 뼈가 생깁니다. 스택 크기를 조정할 수 있지만 IIRC는 ~ 2MB를 가지고 있습니다. 또한 함수를 종료하자마자 스택의 모든 내용이 지워집니다. 따라서 나중에 참조하는 것이 문제가 될 수 있습니다. (할당 된 객체를 쌓는 포인터는 버그로 이어집니다.)

더미

할당 속도가 크게 느려집니다. 그러나 당신은 GB를 가지고 놀았습니다.

가비지 콜렉터

가비지 수집기는 백그라운드에서 실행되고 메모리를 비우는 일부 코드입니다. 힙에 메모리를 할당 할 때는 메모리 누수라고하는 메모리를 잊어 버리는 것이 매우 쉽습니다. 시간이 지남에 따라 응용 프로그램이 사용하는 메모리는 충돌 할 때까지 증가하고 증가합니다. 가비지 수집기가 주기적으로 더 이상 필요없는 메모리를 확보하면이 종류의 버그를 제거하는 데 도움이됩니다. 물론 이것은 가비지 수집기가 속도를 늦추기 때문에 가격이 책정됩니다.


답변

정적 및 스택의 문제점은 무엇입니까?

“정적”할당의 문제점은 컴파일 타임에 할당이 수행된다는 것입니다.이 변수를 사용하여 가변 수의 데이터를 할당 할 수 없습니다.이 수는 런타임까지 알려지지 않았습니다.

“스택”에 할당 할 때의 문제점은 할당이 수행되는 서브 루틴이 반환되는 즉시 할당이 삭제된다는 것입니다.

힙에 변수를 할당하지 않고 전체 응용 프로그램을 작성할 수 있습니까?

아마도 사소하지 않고 일반적이며 큰 응용 프로그램은 아니지만 (소위 “임베디드”프로그램은 C ++의 하위 집합을 사용하여 힙없이 작성 될 수 있습니다).

가비지 수집기는 어떤 기능을 수행합니까?

응용 프로그램이 더 이상 참조하지 않을 때를 감지하기 위해 데이터 ( “표시 및 스윕”)를 계속 감시합니다. 응용 프로그램이 데이터 할당을 해제 할 필요가 없기 때문에 이것은 응용 프로그램에 편리하지만 가비지 수집기는 계산 비용이 많이들 수 있습니다.

가비지 콜렉터는 일반적인 C ++ 프로그래밍 기능이 아닙니다.

이 가비지 수집기를 사용하여 수행 할 수없는 메모리를 직접 조작 할 수 있습니까?

결정 론적 메모리 할당 해제를위한 C ++ 메커니즘을 배우십시오.

  • ‘정적’: 할당 해제되지 않음
  • ‘stack’: 변수가 “범위를 벗어남”하자마자
  • ‘heap’: 포인터가 삭제 될 때 (어플리케이션에 의해 명시 적으로 삭제되거나 일부 서브 루틴 내에서 암시 적으로 삭제됨)

답변

스택이 너무 깊고 스택 할당에 사용 가능한 메모리가 오버플로되면 스택 메모리 할당 (함수 변수, 로컬 변수)이 문제가 될 수 있습니다. 힙은 여러 스레드에서 또는 프로그램 수명주기 내내 액세스해야하는 객체를위한 것입니다. 힙을 사용하지 않고 전체 프로그램을 작성할 수 있습니다.

가비지 수집기없이 메모리를 매우 쉽게 누출 할 수 있지만 개체와 메모리가 해제 될 때 지시 할 수도 있습니다. Java가 GC를 실행할 때 문제가 발생했으며 GC가 독점 스레드이므로 다른 프로세스를 실행할 수 없기 때문에 실시간 프로세스가 있습니다. 따라서 성능이 중요하고 누출 된 객체가 없음을 보장 할 수 있다면 GC를 사용하지 않는 것이 매우 도움이됩니다. 그렇지 않으면 응용 프로그램이 메모리를 소비하고 누출의 원인을 추적해야 할 때 생명을 싫어합니다.


답변

프로그램이 할당 할 메모리 양을 미리 알지 못하는 경우 (따라서 스택 변수를 사용할 수 없음) 링크 된 목록이라고하면 목록의 크기를 미리 몰라도 목록이 커질 수 있습니다. 따라서 힙에 할당하는 것은 얼마나 많은 요소가 삽입되는지 알지 못하는 경우 연결된 목록에 적합합니다.