자원 관리

컴퓨터 메모리는 한정된 자원이므로, 이를 효율적으로 활용하기 위해 다양한 고민이 필요하다.

 

스택 메모리

일반 변수들은 대부분 스택 메모리 공간을 차지합니다 스택 메모리의 가장 큰 특징은 변수의 생존 주기가 끝나면 선언시 할당되었던 메모리가 자동으로 회수된다는 점이다. 

따라서 사용자가 직접 메모리를 해제할 필요가 없다.

단점으로는

  • 일반적으로 할당 가능한 스택 메모리의 크기가 제한적이다.
  • 변수의 스코프(생존 영역)을 벗어나면 자동으로 해제되므로, 메모리를 더 길거나 유연하게 관리하기 어렵다.

스택 메모리 예제

#include <iostream>
using namespace std;

void func1() {
    int a = 10;  // 지역 변수 'a', stack 메모리에서 관리됨
    cout << "func1: a = " << a << endl;
}  // func1()이 종료되면 'a'는 소멸됨

int main() {
    func1();  // func1() 호출
    // 'a'는 func1() 호출 중에만 존재하고, 함수 종료 후 소멸됨
    return 0;

힙 메모리

스택의 단점을 해결하기 위해 사용한다.

  • 동적 할당시 new 연산자를 사용하고, 해제 시 delele 연산자를 사용한다
  • 스택과 달리 자동으로 해제되지 않으므로 메모리 누수 등의 위험이 있을 수 있다.
  • 동적 할당된 객체(또는 변수)의 생존 주기는 사용자가 delete로 해제할 때까지 유지 된다.
#include <iostream>
using namespace std;

void func1() {
    int* ptr = new int(10);  // 힙 메모리에 정수 10 할당
    cout << "Value: " << *ptr << endl;
    delete ptr;             // 메모리 해제
}

int main() {
    func1();
    return 0;
}

스마트 포인터

스마트 원리의 핵심 원리는 new / delete 를 사용하지 않는 자동 메모리 관리이다.

이전에 delete를 직접 호출해서 메모리를 관리하는 방식 대신, 각 스마트 포인터의 목적에 맞게 자동으로 메모리 관리를 한다.

 

1. Unique_ptr

객체에 대한 단일 소유권을 관리.

  • 객체의 소유권을 명확히 하고 소유권 이전을 통해 효율적인 자원관리가 가능
  • move를 통해 소유권을 이동하는 식으로 관리

 

2. shared_ptr

레퍼런스 카운트를 관리

  • 레퍼런스 카운트란 현재 객체를 참조하는 포인터의 개수를 카운팅 한다.
  • 레퍼런스 카운트가 0이 되면 객체는 자동으로 메모리 해제
  • Dangling Pointer 및 MemoryLeak 문제를 효과적으로 방지할 수 있다.

 

3. weak_ptr

객체의 소유권을 공유하지 않는다.

  • 다른 스마트 포인터와 다르게 레퍼런스 카운트를 증가시키지 않는 약한 참조를 한다.
  • shareed_ptr은 유용하지만 순환 참조가 발생할 수 있다. 이 상황에서 서로 순환하고 있는 shared_ptr중 하나를 wak_ptr로 대체하면 순환 고리가 끊어지게 되므로 문제를 해결할 수 있다.
  • shared_ptr은 관찰과 소유를 하는 반면, weak_ptr은 관찰만 한다고 표현한다.

 

얕은 복사와 깊은 복사

얕은 복사란, 클래스 내의 포인터 멤버를 복사할 때 포인터가 가리키는 데이터가 아닌 포인터가 저장하고 있는 주소값만 복사하는 것을 의미

 

깊은 복사는 클래스의 포인터 멤버가 가리키는 동적 데이터를 새로 할당된 독립적인 메모리 영역에 복제한는 것을 의미

 

+ Recent posts