delete는 단일로 동적할당한 메모리를 해제할때,
delete[]는 배열로 동적할당한 다수의 메모리를 해제할 때 사용한다.
따라서 new ↔ delete / new[] ↔ delete[] 이렇게 짝을 맞춰 쓰는 것이 옳다.
malloc은 함수고 new는 연산자(operator)
malloc은 메모리 할당만 해주고 초기값지정을 해주지않는다
new는 메모리할당과 초기화 작업
malloc() + 클래스 생성자 = new / new[]
free() + 클래스 소멸자 = delete / delete[]
new로 할당받은 공간은 realloc같은 함수가 없기에 다시 할당해주어야한다.
따라서 재할당이 많이 일어난 경우에는 malloc이 new보다 효율적일수가있다.
#include <iostream>
using namespace std;
class test {
private:
char str = 'A';
int member = 6;
char arr[3] = { 1,2,3 };
public:
test(){}
~test(){}
};
int main()
{
test* t1 = new test;
test t2 = test{};
cout << sizeof(test) << endl; // 12 4 + 4 + 4
cout << sizeof(t1) << endl; // 8 포인터 변수
cout << sizeof(t2) << endl; // 12 4 + 4 + 4
cout << t1 << endl; // 0x0000013F889E0720
delete t1;
return 0;
}
CD => 힙 영역에서 초기화되지 않은 메모리라고 표시해주는 용도
DD => 동적 할당된 메모리가 해제된 공간을 표시해주는 용도
FD => 동적 할당을 안전하게 하기 위해 처음과 끝에 넣는 가드 바이트(가드 바이트는 4바이트)
delete t1 직전까지의 코드를 실행하였을때 메모리 공간
(fd는 no man's land fill 이라고하여 할당 된 메모리보다 더 큰 데이터를 집어넣었을때 오류나는건 fd때문이라고한다)
delete t1 코드를 실행한 메모리 공간
delete를 한 후 메모리 공간은 fd ~ 값 ~ fd 가 모두 dd로 바뀌었다.
정상적으로 메모리가 반환되었다.
1. new가 tests 객체의 크기를 추론하여 malloc()에게 넘겨준다
2. malloc()은 OS에 해당 크기만큼 메모리 할당 요청을 한다
3. OS가 유효한 주소를 넘겨주고, Heap Manager는 어느 주소에 얼마의 크기를 할당해 주었는지 기록한다.
4. 주소를 받은 malloc()은 객체 포인터에 주소값을 반환한다.
5. new가 할당받은 곳에 객체 생성자를 호출한다.
6. delete가 객체 소멸자를 호출하고, free에게 포인터를 넘겨준다.
7. free는 Heap Manager에게 t1의 주소로 메모리 해제 요청을 한다.
8. Heap Manager가 기록중에 t1의 주소가 있는지 유효성 검사를 하고, 있으면 메모리를 해제한다.
이번에는 new[]를 이용해서 5만큼 할당해주었다
test* t1 = new test[5];
cout << t1 << endl;
delete[] t1;
클래스의 크기 12바이트 * 5만큼 메모리가 할당되어있고 앞 뒤로 fd로 잘 할당된것을 볼수있다.
그런데 05 00 00 000 00 00 00 00 이 더 붙어있다.
이 ?? 부분은 코드에서 작성한 new test [5] 이다.
new[]는 클래스의 크기 + 8바이트의 추가 메모리를 OS에 요청한다. (32비트 os에선 4바이트고 64비트 os에선 8바이트)
OS는 fd다음인 ~CFC00 주소를 준다.
주소를받은 new[] 는 CFC00로 시작하는 맨 앞 8바이트에 배열 크기 값 (5)를 넣어두고 그 다음 주소부터 생성자를 호출하여 12바이트 * 5 만큼 데이터를 할당한다.
그런데 t1의 시작주소는 ~CFC08인데 어떻게 메모리가 해제가 되는것일까?
delete[]가 free() 에게 ~CFC08 - 8을 넘겨준것
Heap Manger은 ~CFC00가 기록에 있으니 유효해서 메모리 해제 요청을 승인한것이다.
---->
new[]가 맨 앞 8바이트 (64비트 OS)에 5를 기록하였던 이유는 소멸자 호출때문이다.
new[]는 컴파일시가아닌 러닝타임에 동작된다.
할당한 객체가 여러개일때 할당한 크기만큼 여러번 소멸자를 호출해야하는데 이 delete[]가 얼만큼 할당하였는지 알 방도가 없기때문에 애초에 new[] 시 8바이트에 할당한 크기를 기록하는것이다.
따라서 new[]가 처음부터 공간을 좀 더 받아내 자신이 할당한 크기를 기록하고, delete[]가 t1의 이전 8바이트 메모리를 역참조하여 얼마나 소멸자를 호출해야 할 지 알아내기로 한 것이다.
1. new[]가 tests 객체의 크기를 추론하여 malloc()에게 (객체 크기 * 객체 개수 + 8바이트) 를 넘겨준다
2. malloc()은 OS에 해당 크기만큼 메모리 할당 요청을 한다
3. OS가 유효한 주소를 넘겨주고, Heap Manager는 어느 주소에 얼마의 크기를 할당해 주었는지 기록한다.
4. 주소를 받은 malloc()은 객체 포인터에 주소값 + 8바이트 를 반환한다.
5. new[]가 할당받은 곳 맨 앞 8바이트엔 객체의 개수를 기록하고, t1의 주소부턴 객체 생성자를 호출한다.
6. delete[] 가 t1 - 8바이트 주소에 들은 객체 크기만큼 객체 소멸자를 호출하고, free에게 t1 - 8의 주소를 넘겨준다.
7. free는 Heap Manager에게 t1 - 8의 주소로 메모리 해제 요청을 한다.
8. Heap Manager가 기록중에 t1 - 8의 주소가 있는지 유효성 검사를 하고, 있으면 메모리를 해제한다.
차이점
new / delete 는 포인터의 시작주소를 넘겨주고
new[] / delete[] 는 포인터의 시작주소 - 8바이트 를 넘겨준다.
따라서 new와 delete[]를 같이사용하거나 new[] 와 delete사용 시 메모리 할당이 제대로 이루어지지않는다.
타입이 클래스가 아니라면..?
new int[5]
이런 식으로 다른 원시 타입 선언을 하면 어떻게 될까?
기본 타입인 int, char 등은 소멸자가 없다. 따라서 배열 크기를 알려 줄 필요가 없기 때문에,
new, new[], delete, delete[]를 혼용해 사용해도 기능 차이가 없어서, 에러가 나지 않는다.
소멸자가 없는 클래스라면..?
new[]와 delete[]의 기능이 약간 달랐던 것은, 소멸자 호출이 원인이었다.
따라서 소멸자가 없는 클래스는 저 둘을 사용해도 배열의 크기를 저장하지는 않는다.
하지만 소멸자는 컴파일러의 필요 판단 하에 묵시적 디폴트로 생성될 수도 있으니, 혼용하는 것은 매우 위험하다.
앞으로 C++ 문법이 어떻게 변경될 지도 모르고, 훗날의 유지보수가 힘들어 질 수 있기에
new는 delete / new[]는 delete[], 이렇게 목적과 기능이 같은 친구들끼리 꼭 짝을 맞춰서 코딩하도록 하자.
만약 혼용으로 인해 Heap Manager가 유효성 검사에서 주소의 기록을 찾지 못하면 에러가 난다.
[출처] [C++]new와 new[], delete와 delete[]의 차이, 구분해야 하는 이유 총정리|작성자 큐 Queue
[C++]new와 new[], delete와 delete[]의 차이, 구분해야 하는 이유 총정리
도대체 왜 delete와 delete[]를 구분해서 쓰라는건지 모르겠는 와중에 이 포스트를 접했다면 당신은 행운아...
blog.naver.com
'프로그래밍 > C++' 카테고리의 다른 글
C++ 람다 예제 (0) | 2023.01.02 |
---|---|
Thread (0) | 2022.06.15 |
이동생성자 / move / forward (0) | 2022.05.22 |
객체 생성시 괄호와 중괄호 (0) | 2022.05.19 |
스마트 포인터 (0) | 2022.05.16 |