C++의 메모리 누수 관리방법

저는 불법입니다.·2025년 8월 24일

메모리 관리의 중요성

우리는 임베디드 시스템에서는 메모리가 아주 작은 용량의 메모리관리가 필요하다. 그러면, 메모리할당과 해제는 매우 중요한 과제가 될 것이다. 만약, 메모리가 넘쳐버리면? 동작하던 기기는 작동을 멈추게 될 것이다.

그렇기 때문에, 프로그래머는 메모리를 관리하는 방법을 꼭 알아야 한다.

우리는 Java를 사용했다고 하면, 동작 방식만 알면 된다. JVM위에 GC가 지속적으로 사용하지 않는 메모리는 알아서 수거해가는 역할을 한다. 하지만, 메모리를 직접 다루는 C++의 경우에는 개발자가 직접 메모리 할당과 해제를 관리해줘야 한다. C++은 자바가 아니기때문에, JVM에서의 GC같은 동작을 하지 않는다.


메모리 할당

메모리 할당은 다음과 같다.

int *p = new int(10);

10이라는 값을 저장하는 int객체를 만드는데, 우리는 이걸 포인터로 저장할 것이다. (참고로 cpp이다.)

만약 c라면 다음과 같다

int *p = (int*) malloc(sizeof(int));
*p = 10;

그리고, 일단 간단한 차이인데, 알고가면 좋을건, 기본적으로 malloc은 메모리만 할당하고, new int()방식은 필수적으로 값을 넘겨줘야 한다.

우리는 이 10이라는 4바이트 공간을 사용하고 난 후에 반납하는 과정을 거쳐야 한다.

delete(p); // C++
free(p); // C

따로보지말고 이제 하나로 봐보자.

int main() {
    int *p = new int(10);
    delete p;
}
younho@gim-yunhoui-MacBookAir algorithm % cd "/Users/younho/jungle/algorithm/" && g++ ex.cpp -o ex &
& "/Users/younho/jungle/algorithm/"ex

컴파일 하면 별 오류 없이 잘 실행된다. 하지만, delete를 2번 해보자.

int main() {
    int *p = new int(10);
    delete p;
    delete p;
}
younho@gim-yunhoui-MacBookAir algorithm % cd "/Users/younho/jungle/algorithm/" && g++ ex.cpp -o ex &
& "/Users/younho/jungle/algorithm/"ex
ex(9990,0x206900c80) malloc: Double free of object 0x13d605d70
ex(9990,0x206900c80) malloc: *** set a breakpoint in malloc_error_break to debug
zsh: abort      "/Users/younho/jungle/algorithm/"ex

엥 이번에는 갑자기 오류가 났다. 왜냐면, double free of object 가 발생한 것이다.

한번 print로 0x13d605d70 이 주소에 double delete가 발생했는지 보자,

int main() {
    int *p = new int(10);
    cout << p << "\n";
    delete p;
    delete p;
}
younho@gim-yunhoui-MacBookAir algorithm % cd "/Users/younho/jungle/algorithm/" && g++ ex.cpp -o ex &
& "/Users/younho/jungle/algorithm/"ex
0x12a605d70
ex(10421,0x206900c80) malloc: Double free of object 0x12a605d70
ex(10421,0x206900c80) malloc: *** set a breakpoint in malloc_error_break to debug
zsh: abort      "/Users/younho/jungle/algorithm/"ex

찍어보니 맞다. 그렇다면, delete는 할당이 안되어져 있는 상태에서 또 호출하게 되면 문제가 발생한다.

이뿐만 아니다. 이미 해제되어져 있는 메모리에 또다시 접근하려고 잘못된 해제(dangling reference) 문제를 야기시킬 수 있다.

우리는 이렇게 직접 메모리를 관리해줘야 하는데, 하나라도 해제하거나, 해제하지 않은상태로 머물러져있다면, 메모리는 사용이 끝나 있더라도 메모리에 계속 올라가있는 상황이 발생하거나 또는 에러로 프로그램이 런타임에 터지게되는 상황이 발생할 수 있다.


그래서 나온 C++11에 나온 도구, smart pointer

스마트포인터는, 기존 포인터처럼 문제상황이 발생하지 않도록 메모리를 자동으로 관리해주는 도구이다.

조금더 자세히 설명하자면, 스마트 포인터는 C++의 특별한 클래스 타입이고, 포인터처럼 동작하지만 메모리를 스스로 관리해준다. 객체가 더이상 필요하지 않으면 자동으로 삭제되는데 자바에서의 GC와 동일하게 관리해주는 것이다.

문법을 간단하게 사용해보자.

#include <memory>
#include <iostream>

using namespace std;

int main() {
	unique_ptr<int> smart_ptr(new int (10));
	cout << *smart_ptr << "\n"
	cout << smart_ptr.get() << "\n";
}
10
0x14bf04330

이렇게 확인 할 수 있다. 그러면, smart_ptr의 주소자체에는 값이 저장된다는 말이다.

자 다시, 조금만 더 보자

int main() {
	unique_ptr<int> smart_ptr(new int (10));
	cout << *smart_ptr << "\n";
	cout << smart_ptr.get() << "\n";
  cout << smart_ptr << "\n";
}
10
0x15a704080
0x15a704080

이렇게 찍힌다. 그러면, 지금 같은 주소를 가리킨다고 하는건데, 의문이 든다. smart_ptr은 그냥 주소만 저장하는건데, 결국 그냥 포인터만 써도 되는거 아닌가? 싶다. 다음 예제를 보자.

int main() {
	unique_ptr<int> smart_ptr1(new int (10));
    unique_ptr<int> smart_ptr2;
    cout << "before \n";

    // cout << "smart_ptr1 = " << *smart_ptr1 << "\n";
    cout << "smart_ptr1 addr = " << smart_ptr1 << "\n";
    // cout << "smart_ptr2 = " << *smart_ptr2 << "\n";
    cout << "smart_ptr2 addr = " << smart_ptr2 << "\n";

    smart_ptr2 = move(smart_ptr1);
    
    cout << "after \n";

    // cout << "smart_ptr1 = " << *smart_ptr1 << "\n";
    cout << "smart_ptr1 addr = " << smart_ptr1 << "\n";
    // cout << "smart_ptr2 = " << *smart_ptr2 << "\n";
    cout << "smart_ptr2 addr = " << smart_ptr2 << "\n";
	
    return 0;
}
before 
smart_ptr1 addr = 0x139e05d70
smart_ptr2 addr = 0x0
after 
smart_ptr1 addr = 0x0
smart_ptr2 addr = 0x139e05d70

보이는 것처럼, 나는 ptr1을 전혀 손대지 않았는데, 포인터 자체가 nullptr이 되었다.

여기서 나오는 개념이 소유권 개념이 등장하게 된다.

위에서 new int (10) 으로 초기화한 값의 소유권이 완전히 ptr2에게 넘어간 상황인 것이다. 이러면, 우리는 ptr1을 따로 초기화해주지 않더라도 메모리의 누수를 방지할 수 있다.

이제 이정도만 이해했으면, 우리는 다음스탭으로 넘어가서 여러 기능들을 봐야 한다. 이정도만 알고 이제 compiler 프로젝트에 대한 설계, 구현이 쉬워지고 이해하는데 어려움이 없을 것이다.


smart pointer의 몇가지 함수 예제

1. get()

내부 raw pointer를 가져오고, 소유권은 유지한다.

auto p = std::make_unique<int>(42);

2. release()

내부 raw pointer를 반환하고, unique_ptr은 소유권을 잃는다. 추가로 직접 delete 해야 함

auto p = std::make_unique<int>(42);
int* raw = p.release();
delete raw;

3. reset()

현재 객체를 delete 하고, 새 포인터로 교체한다. (없으면 그냥 해제)

auto p = std::make_unique<int>(42);
p.reset(new int(100)); // 42는 해제되고, 100으로 교체
p.reset(); // 현재 소유한 포인터 해제 → nullptr

4. operator*, operator→

*P → 값에 접근

p→ 멤버에 접근

auto p = std::make_unique<std::string>("hello");
std::cout << (*p) << "\n";       // hello
std::cout << p->size() << "\n"; // 5
std::cout << p->at(0) << "\n"   // h

5. SWAP()

두 unique_ptr의 소유권을 맞바꿈

auto p1 = std::make_unique<int>(1);
auto p2 = std::make_unique<int>(2);
p1.swap(p2);

6. Operator bool

포인터가 유효한지 (nullptr) 아닌지 확인 가능

auto p = std::make_unique<int>(10);
if (p) std::cout << "소유 중\n";
p.reset();
if (!p) std::cout << "비어 있음\n";

이제 추가로 써야하는 함수는 천천히 추가하고, compiler 만들러 가자

(이거 공부한 이유, ast에서 free를 해주는 경우가 너무 많아서)

profile
만지면 300만원 내야해요, 참고로 호주에 삽니다.

2개의 댓글

comment-user-thumbnail
2025년 8월 24일

smart pointer을 쓰면 메모리 누수 방지에 정말 효과적이겠네요! 다음 포스팅이 기대됩니다~ raw pointer와 소유권 개념에 대해서도 자세히 다뤄주시면 좋겠습니다^^

1개의 답글