요약
반드시 malloc/free, new/delete, new[]/delete[] 짝을 맞춰서 할당하고 해제해야 한다.
짝을 맞추지 않는 경우 어떤 문제가 발생하는가?
•
new[]로 생성한 클래스 배열을 delete로 제거하는 경우 첫 번째 클래스를 제외한 모든 클래스의 소멸자가 호출되지 않아 메모리 누수가 발생할 수 있다.
객체 소멸 시점
•
전역 객체: 프로그램이 종료되는 시점에 소멸자가 호출된다.
•
지역 객체: 해당 함수가 종료되는 시점에서 소멸자가 호출된다.
•
동적 객체: delete / delete[] 호출 시 소멸자가 호출된다. (free는 소멸자 실행X)
할당/해제
•
malloc/free : 힙 관리자를 통해 넘긴 인자만큼의 메모리 공간을 할당받고 해제한다.
•
new/delete : 내부적으로 malloc/free를 호출하며 추가적으로 생성자와 소멸자를 호출한다.
•
new[]/delete[] : new/delete와 비슷하지만 소멸자가 있는 타입은 할당 구조가 다르다.
void main()
{
int* p1 = (int*)malloc(sizeof(int));
*p1 = 1;
int* pArr1 = (int*)malloc(sizeof(int)*2);
pArr1[0] = 0;
pArr1[1] = 1;
free(p1);
free(pArr1);
}
C
복사
void main()
{
int* p2 = new int;
*p2 = 2;
int* pArr2 = new int[2];
pArr2[0] = 2;
pArr2[0] = 3;
delete p2;
delete [] pArr2;
}
C++
복사
설명
malloc & free
void * malloc(size_t size);
void free(void* memblock);
C
복사
•
C 내부적으로 CRT(C Runtime Library)의 힙 매니저가 malloc으로 할당받은 메모리 블록의 주소와 크기를 관리하며 free 요청에 대응한다.
◦
malloc은 요청한 size만큼의 메모리를 할당해 할당된 메모리 블록의 주소를 반환한다.
◦
free에는 malloc/calloc/realloc으로 할당받은 메모리 블록의 주소을 해제한다. 만일 유효하지 않은 주소를 free에 넘기게 될 경우 어떤 에러가 발생할지 알 수 없다.
<그림 1>처럼 힙 관리자는 메모리 블록을 할당할 때마다 메모리 할당 내역을 저장하게 된다. 그림의 할당 내역 테이블에는 p1, p2, p3처럼 이름이 들어가 있지만, 실제로는 p1, p2, p3의 메모리 주소 가 들어가게 된다. free를 호출하게 될 경우 힙 관리자는 인자로 넘 어온 메모리 주소를 할당 내역에서 검색해 존재할 경우 할당된 크기만큼 해당 메모리 블록을 해제하고, 할당 내역에서 삭제한다.
단일 객체 처리 과정 (new / delete)
내부적으로 malloc / free를 호출하는 것은 동일하나 추가적으로 생성자와 소멸자를 호출한다.
new Ctest
1.
Ctest의 크기만큼 힙에 할당한다. (malloc)
2.
Ctest의 생성자를 호출한다.
delete pT
1.
pT 포인터 타입의 소멸자를 호출한다.
2.
pT 주소 그대로 힙 할당을 해제한다. (free)
free 시 객체가 가지고 있는 소멸자가 아닌 포인터 타입의 소멸자가 호출된다!
•
아래의 경우 A 객체지만 B 포인터를 해제했으므로 B의 소멸자가 호출된다.
class CTestA {
public:
CTestA() {}
~CTestA() { cout < < "CTestA::Destructor!" < < endl; }
};
class CTestB {
public:
CTestB() {}
~CTestB() { cout < < "CTestB::Destructor!" < < endl; }
};
void main() {
CTestA* pA = new CTestA; // A의 생성자 호출
CTestB* pB = (CTestB*)pA; // ①
delete pB; // ② B의 소멸자 호출
}
# 출력 결과
CTestA::Constructor!
CTestB::Destructor!
C++
복사
배열 객체 처리 과정 (new []/ delete[])
•
객체에 소멸자가 없는 경우, new/delete와 동일한 메모리 구조를 할당 받는다.
•
객체에 소멸자가 있는 경우, 배열 요소의 개수를 알기 위해 공간을 추가적으로 할당 받는다.
delete는 단일 객체로 판단하기 때문에 배열 객체를 delete 하면 소멸자는 한 번만 호출되고, 주소를 그대로 넘기기 때문에 힙 관리자가 할당 내역을 찾을 수 없어 메모리가 제대로 해제되지 않는다.
소멸자가 있는 경우 (ex. class)
new Ctest[n]
1.
4 + n * sizeof(Ctest)만큼 힙에 할당한다.
2.
Ctest의 생성자를 n 번 호출한다.
3.
pT에 할당된 주소 + 4를 반환한다.
delete [] pT
1.
pT - 4로 배열 요소의 개수 n을 확인한다.
2.
n 만큼 소멸자를 호출하고 힙 할당을 해제한다.
소멸자가 없는 경우 (ex. int)
•
new/delete와 동일하게 동작한다.
•
단, 컴파일러 내부에서 암시적으로 소멸자가 선언된 경우 다르게 동작하므로 주의가 필요하다.