C/C++ 환경에서는 런타임에서 동적으로 메모리를 할당 해 줄수 있다. 이때 동적 할당 되는 메모리는 힙 영역에 할당된다. 프로그램에서 메모리가 동적 할당 되는 것은 유저 영역, 커널 영역 중에서 커널 영역에서 담당한다. 유저 영역은 프로그램이 실행 되는 영역, 커널 영역은 운영체제가 관리하는 영역인데, 유저 영역의 프로그램에서 API를 통해서 커널 영역에 메모리 할당을 요청하면, 커널이 메모리를 할당하여 유저 영역의 프로그램에게 넘겨주게 된다.
C 스타일의 메모리 동적 할당은 malloc/free를 통해서 이루어 지는데, 해당 함수들은 아래 포스트에 자세하게 설명되어 있다.
위 포스트에선 메모리 할당 함수에서 반환된 포인터의 타입이 void인데, 이는 해당 메모리 블럭의 크기가 불확실 하다는 것을 의미한다. 따라서 형변환을 통해 포인터가 할당받은 메모리의 크기를 표현해 주어야 한다.
C++ 환경에서는 new/delete, new[]/delete[]를 통해서 메모리를 할당하고 해제 할 수 있다. 해당 방법은 C 언어에서와 다르게 함수가 아니라 연산자를 이용한다는 차이점이 있다. 또한 new를 통한 메모리 할당은 할당할 메모리의 크기를 자동으로 연산해주기 때문에, 명시적으로 메모리의 크기를 알려주지 않아도 된다.
new[] 연산자는 메모리를 연속된 공간에 할당할때 사용하는 연산자이다. 해당 연산자의 [] 내부에 할당할 객체의 개수를 넣으면 개수 * 객체의 크기 만큼 연속된 메모리 공간에 메모리가 할당된다. 해당 연산자를 사용해서 메모리를 할당하였으면, 반드시 delete[] 연산자로 할당을 해제 해 주어야 한다.
int* numPtr = new int; // sizeof(int) 만큼의 크기를 할당
int* numPtrs = new int[5]; // 5 * sizeof(int) 만큼의 크기를 할당 한 후 해당 메모리의 첫 주소를 반환
// numPtrs의 접근 방법
int* numPtr1 = numPtrs[0]; // 배열과 같이 접근
int* numPtr2 = (numPtrs + 1) // 주소 연산을 통해서 접근
new[] 연산자가 반환하는 주소는 할당한 메모리 블럭의 첫 주소를 가르키기 때문에 [] 연산자를 사용해 배열 처럼 접근하거나 ptr + 1과 같이 연산을 통해서 다음 객체에 접근 가능하다.
C언어의 malloc과 free 함수와 다르게, C++의 new/delete 연산자는 객체를 동적 할당 할 때 생성자와 소멸자를 자동으로 호출 해 준다. 또한 new[]/delete[] 연산자를 통해 할당, 해제 할때는 해당 횟수 만큼의 생성자와 소멸자를 호출 해 준다.
메모리의 동적 할당은 개발자가 자유롭게 메모리를 할당하고 사용할 수 있도록 해 주지만, 예상치 못한 오류를 일으킬 가능성도 높아진다.
힙 오버플로우는 메모리를 할당만 하고 해제 하지 않았을때, 즉, 메모리 누수가 발생한 경우에 프로그램의 힙에 할당된 메모리 공간을 넘어서서 초과할 경우 발생하는 오류이다. 이를 예방하기 위해서 사용이 종료된 메모리는 반드시 할당을 해제 하는 것이 중요하다.
해당 오류는 이미 할당 해제한 메모리를 또 다시 할당 해제 할때 발생 하는 오류이다. 이는 할당 해제 한 메모리의 포인터에 nullptr을 대입 함으로서 예방 가능하다.
이 오류는 가장 위험하고 감지하기 힘든 오류이다. 해당 오류는 이미 할당 해제된 메모리에 접근하려고 할때 발생하는데, 이는 할당 되지 않은 메모리 공간에 접근 하는 것이기 때문에 포인터가 어떤 값을 가르킬지 모르고, 이미 다른 포인터를 통해 사용중인 메모리에 침범 할 수도 있기 때문에 가장 위험하다. 이 오류를 예방하는 방법 또한 할당 해제 한 메모리의 포인터에 nullptr을 대입 하는 것이다.