[C++] 동적 메모리 할당(Heap)

👻 동적 메모리 할당

동적 메모리 할당은 컴퓨터 프로그래밍에서 실행 시간 동안 사용할 메모리 공간을 할당하는 것을 말한다. 스택과 비슷한 것 같지만 생성/소멸 시점을 관리할 수 있다는 점이 다르다. 필요할 때만 사용하고 필요없으면 반납할 수 있는 공간을 의미하며 Heap 영역이 여기에 속한다. 동적 할당과 연관된 함수 혹은 연산자는 malloc, free, new, delete, new[], delete[]가 있다.


🌱 malloc

메모리를 동적으로 할당해주는 명령어이다.

void* __cdecl malloc(size_t _Size)

메모리 할당 후 시작 주소를 가리키는 void형 포인터를 반환하고 인자로 할당 받을 메모리의 크기를 받는다. 메모리가 부족하면 nullptr를 반환한다.

void* pointer = malloc(1000);
Monster* m1 = (Monster*)pointer;

// 바로 사용 가능
Monster* m2 = (Monster*)malloc(sizeof(Monster));

💡 void*는 무엇일까?
👉 *이 있으니 포인터는 맞지만 void가 붙어있어서 아무것도 없다는 것처럼 보인다. 하지만 그 의미가 아닌 알 수 없으니 포인터로 받아서 변환해서 사용하라는 의미이다.


🪐 Heap Overflow

유효한 힙 범위를 초과해서 사용하면 나타나는 문제이다.

void* pointer = malloc(4);
Monster* m1 = (Monster*)pointer;

👉 Heap Overflow 발생. 참고로 Monster의 크기는 12바이트이다.


🌱 free

malloc(혹은 기타 calloc, realloc 등의 사촌)을 통해 할당된 영역을 해제할 때 사용하는 명령어이다.

void __cdecl free(void *_Block)

힙 관리자가 할당/미할당 여부를 구분해서 관리한다.

💡 할당 해제할 메모리의 크기는 어떻게 알까?
👉 malloc 등을 이용해 메모리를 할당받기 전, 할당 받을 메모리의 크기가 담긴 정보를 헤더에 저장해놓는다. free를 이용해 할당 해제할 때 해당 데이터를 이용하여 해제하게 된다.


🪐 메모리 누수

메모리를 할당 후 해제하지 않으면 메모리 누수가 발생한다. 사용했던 메모리가 계속 쌓여 결국엔 힙 영역 전체를 차지하게된다.


🪐 Double free

메모리 할당 해제를 여러번 하는 것이다. 대부분 두 번째 할당 해제 시에 이미 유효하지 않은 값이 들어있는 걸 체크했기 때문에 크래시만 나고 끝난다.


🪐 Use-After-Free

가장 위험한 에러이다. 메모리를 할당 해제한 후에 다시 접근하여 사용하면 발생하는 문제이다. Double free와 다르게 바로 크래시가 나지 않고 다른 부분을 건드리게 되기 때문에 위험하다.


🌱 new / delete

malloc/free와 같은 기능으로 동작한다. new는 메모리 할당을, delete는 할당 받은 메모리를 해제한다.

malloc/free는 C에서부터 이어져 왔지만 new/delete는 C++에서 추가된 문법이다. 그리고 malloc/free함수이지만 new/delete연산자이다.

// 메모리 할당
Monster* m3 = new Monster;
m3->_hp = 300;
m3->_x = 3;
m3->_y = 6;

// 메모리 할당 해제
delete m3;

각각을 세트로 사용해야 하고 혼합해서 사용할 수 없다. 또한, 위에서 봤던 것처럼 같은 에러들이 존재하니 조심해야한다.

malloc/free vs new/delete

  • 사용 편의성 👉 new/delete 승!
  • 타입에 상관없이 특정한 크게의 메모리 영역을 할당받으려면? 👉 malloc/free 승!
  • 그런데 둘의 가장 근본적인 중요한 차이는 따로 있다.
    new/delete는 (생성타입이 클래스일 경우) 생성자와 소멸자를 호출해준다.

🌱 new[] / delete[]

new/deletemalloc/free와 다르게 할당받을 메모리의 크기를 직접적으로 설정할 수 없다. 이러한 점을 보완하기 위해 배열이 추가된 문법이 존재한다.

// 메모리 할당
Monster* m4 = new Monster[5];
m4->_hp = 400;
m4->_x = 4;
m4->_y = 8;

// m4의 다음 주소에 할당
Monster* m5 = (m4 + 1);
m5->_hp = 500;
m5->_x = 5;
m5->_y = 10;

// 메모리 할당 해제
delete[] m4;

사용법은 비슷하나 배열이 추가된 점이 다르고, 이렇게 할당하면 m4의 크기는 60(12*5)이 된다. 포인터이므로 1을 더해 다음 주소에도 값을 추가할 수 있지만 메모리 할당을 해제할 때는 똑같이 배열 표시를 붙여줘야하고 위의 방식으로 m4를 할당 해제하면 m5의 값들도 모두 사라지게 된다.


🌱 유저 영역과 커널 영역

우리가 흔히 말하는 운영체제가 포함된 환경은 크게 유저 영역커널 영역으로 나뉜다.

유저 영역에는 메모장, LOL, 곰플레이어같은 프로그램이 있고 커널 영역에는 Windows 등 운영체제의 핵심 코드가 들어있다.

유저 영역의 각 프로그램은 서로가 없는 것처럼 독립적으로 실행되며 운영체제에서 제공하는 API를 호출하게 되면 커널 영역에서 메모리를 할당한 후에 건네주게 된다.

C++에서는 기본적으로 CRT(C 런타임 라이브러리)의 힙 관리자를 통해 힙 영역을 사용한다. 단, 정말 원한다면 직접 API를 통해 힙을 생성하고 관리할 수도 있다. (MMORPG 서버 메모리 풀링이 있다.)


👻 글을 마치며

이번 시간에는 힙 영역에 대해 알아보고 다양한 문법들을 알아보았다. 예전에 스쳐지나가면서 봐왔던 문법들이 이제서야 이해가 된다. 또, 힙과 스택의 차이에 대해 알게 되었다. 생각보다 쉬웠고 내가 몰랐을 때에도 많이 사용했었던 문법인데 알고 나니 더 잘 쓸 수 있게 된 것 같다.


소스코드 보러가기


출처
인프런 Rookies님 강의

Categories:

Updated:

Leave a comment