[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/delete
는 malloc/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 서버 메모리 풀링이 있다.)
👻 글을 마치며
이번 시간에는 힙 영역에 대해 알아보고 다양한 문법들을 알아보았다. 예전에 스쳐지나가면서 봐왔던 문법들이 이제서야 이해가 된다. 또, 힙과 스택의 차이에 대해 알게 되었다. 생각보다 쉬웠고 내가 몰랐을 때에도 많이 사용했었던 문법인데 알고 나니 더 잘 쓸 수 있게 된 것 같다.
Leave a comment