와와

힙(Heap)과 스택(Stack)과 메모리 할당과 해제 본문

개발/C++

힙(Heap)과 스택(Stack)과 메모리 할당과 해제

정으주 2024. 2. 5. 19:01

스택 메모리

- 정적 할당, 컴파일 시간에 크기가 결정됨, 함수 호출 시 자동 할당되고 함수 종료 시 자동 해제됨

- LIFO(Last In First Out)방식으로 관리되며, 메모리 관리가 간단하고 빠르다. 스택의 크기는 제한적이기 때문에 큰 데이터를 저장하기에는 적합하지 않다.

 

정적 할당: Static Allocation

- 변수/객체의 크기와 수명이 컴파일 시간에 결정된다.

 

 

 

 

힙 메모리

- 동적 할당, 프로그램 실행 중에 크기가 결정되며, 개발자가 'new' 연산자를 사용하여 메모리를 할당하고, 'delete' 연산자를 사용하여 메모리를 해제한다.

- 스택에 비해 관리가 복잡하고 할당 및 해제 속도가 느리다. 그러나 메모리의 크기 제한이 스택보다 유연하여 큰 데이터를 저장하기에 적합하다.

 

동적 할당: Dynamic Allocation

- 프로그램 실행 중에 변수/객체의 크기가 결정된다. 할당 해제를 수동으로 관리해주어야 함.

 

 

 

 

 

 

메모리 구조

- 코드 영역: 프로그램의 실행 코드가 저장됨

- 데이터 영역: 전역, 정적 변수가 저장됨. 프로그램 시작 시 할당, 종료 시 해제

- 스택 영역: 함수의 지역 변수와 매개변수가 저장되는 영역. 함수 호출 시 할당, 함수 반환 시 해제

- 힙 영역: 동적으로 할당된 데이터(예: 'new'연산자로 생성된 객체)가 저장되는 영역. 사용자가 직접 관리

 

 

저장되는 영역과 인스턴스를 예시로 보자면

#include <iostream>
using namespace std;

int globalVar = 10; // 데이터 영역: 전역 변수

void function(int param) { // 코드 영역: function 함수의 코드
    static int staticVar = 5; // 데이터 영역: 정적 변수
    int localVar = 20; // 스택 영역: 지역 변수
}

class DynamicObject {
public:
    DynamicObject() {
        cout << "DynamicObject 생성" << endl;
    }
    ~DynamicObject() {
        cout << "DynamicObject " << endl;
    }
};

int main() {
    int mainLocalVar = 30; // 스택 영역: main 함수의 지역 변수
    function(mainLocalVar); // 스택 영역: function의 매개변수 'param'

    DynamicObject* dynamicObj = new DynamicObject(); // 힙 영역: 동적으로 할당된 객체

    delete dynamicObj; // 힙 영역의 동적으로 할당된 객체 해제
    return 0;
}

 

이런 느낌이다.

 

 

 

 

 

 

 

힙 메모리 해제에 관하여

 

힙 메모리 관리는 프로그램의 성능과 안전성에 매우 중요한 요소이다.

프로그램이 종료될 때 운영 체제는 프로세스에 할당된 모든 리소스와 메모리를 회수하기 때문에 프로그램 실행 중에 명시적으로 해제하지 않은 메모리도 프로그램 종료 시 운영 체제에 의해 자동으로 해제된다.

하지만 프로그램이 실행되는 동안 메모리 사용량이 증가하는 메모리 누수, 성능 저하가 발생할 수 있기 때문에, 최적의 성능을 유지하기 위해 적절한 시점에 메모리를 해제하는 것이 필수적이다. 그럼 어떻게 언제 해제할까?

 

 

1. 더 이상 사용하지 않을 때

2. 소유권과 책임이 명확한 구조에서 

 

 : 객체나 데이터에 대한 소유자가 메모리 할당과 해제의 책임을 지도록 한다. 예를 들어, 객체를 생성하고 관리하는 클래스나 모듈이 있다면, 해당 클래스나 모듈이 객체의 생명주기를 관리하며 필요 없어진 객체를 해제해야 함

3. 자원 관리자나 스마트 포인터를 사용할 때

: C++에서는 RAII(Resource Acquisition Is Initialization) 패턴을 사용하여 자원(메모리 포함)을 관리하는 것이 일반적입니다. 스마트 포인터(std::unique_ptr, std::shared_ptr)를 사용하면, 객체의 참조가 더 이상 필요하지 않게 되면 자동으로 메모리를 해제할 수 있습니다. 이 방법은 메모리 관리를 자동화하고 누수를 방지하는 데 매우 유용하다.

 

4. 프로그램의 논리적 단계나 특정 지점에서

: 프로그램이 특정 논리적 단계나 작업을 완료했을 때 메모리를 해제하는 것도 좋은 방법이. 예를 들어, 게임에서 한 레벨을 완료한 후 또는 대규모 데이터 처리 작업이 끝난 후에는 더 이상 필요하지 않은 데이터를 해제할 수 있다.

 

 

 

 

스마트 포인터

C++에서는 std::unique_ptr, std::shared_ptr, std::weak_ptr 등 여러 종류의 스마트 포인터를 제공한다.

#include <iostream>
#include <memory> // 스마트 포인터를 위한 헤더 파일

class MyClass {
public:
    MyClass() {
        std::cout << "MyClass 생성자 호출" << std::endl;
    }
    ~MyClass() {
        std::cout << "MyClass 파괴자 호출" << std::endl;
    }
    void myFunction() {
        std::cout << "MyClass의 myFunction 호출" << std::endl;
    }
};

int main() {
    std::unique_ptr<MyClass> myObject = std::make_unique<MyClass>();
    myObject->myFunction();
    // myObject가 범위를 벗어날 때 자동으로 메모리 해제
    return 0;
}

 

이 예제에서 SampleClass의 인스턴스가 생성될 때 동적으로 메모리가 할당되고, 인스턴스가 소멸될 때 파괴자가 호출되어 동적으로 할당된 메모리가 해제된다.