- 메모리 할당 및 생성자 호출
- 소멸자 호출 및 메모리 할당 해제 ( 메모리 할당 해제 후 0x8123 )
- 포인터가 nullptr일 경우에 대한 처리가 되어있다. (null체크 후 return)
int main()
{
int* pa = new int;
std::cout << std::format("pa Address {:#x} before delete\n", (INT_PTR)pa) << std::endl;
delete pa;
std::cout << std::format("pa Address {:#x} after delete\n", (INT_PTR)pa) << std::endl;
return 0;
}
int* pa = new int는 int size만큼을 malloc 후 종료한다.
마찬가지로 delete도 건네준 포인터에 대해서 free를 바로 하는 것을 확인할 수 있다.



int main()
{
int* pa = new int[5];
std::cout << std::format("pa Address {:#x} before delete\n", (INT_PTR)pa) << std::endl;
delete[] pa;
std::cout << std::format("pa Address {:#x} after delete\n", (INT_PTR)pa) << std::endl;
return 0;
}
new / delete와 마찬가지로 처음 malloc 크기만 달라질 뿐 malloc 후 delete 하는 것은 다른 점이 크게 보이지 않는다.




그렇기 때문에 기본 자료형에 대해서
{
int* pa = new int[10];
delete pa;
}
// 또는
{
int* pa = new int;
delete[] pa;
}
로 코드가 짜여졌더라도 문제없이 실행이 될 것이라고 생각하고 실행, 문제가 없었다
// ---- Test ----
class Test
{
public:
Test() : x(0x01234567), y(0x89abcdef),z(0xaabb) { std::cout << std::format("Test Constructor..") << std::endl; }
~Test() { std::cout << std::format("Test Destructor....") << std::endl; }
private:
int x;
int y;
short z;
};
int main()
{
Test* pa = new Test;
std::cout << std::format("pa Address {:#x} before delete\n", (INT_PTR)pa) << std::endl;
delete pa;
std::cout << std::format("pa Address {:#x} after delete\n", (INT_PTR)pa) << std::endl;
return 0;
}
new의 경우 call operator new를 통해 객체의 사이즈만큼 메모리를 할당받아온 후 null체크를 진행하고, 할당받은 주소를 토대로 생성자를 호출하는 것을 확인할 수 있다.


delete의 경우 scalar deleting destructor라는 함수에서 this포인터를 이용해 소멸자 호출과 delete함수를 호출한다.
delete내부에서는 free함수를 통해 사용된 메모리를 해제한다.
class Test
{
public:
Test() : x(0x01234567), y(0x89abcdef),z(0xaabb) { std::cout << std::format("Test Constructor..") << std::endl; }
~Test() { std::cout << std::format("Test Destructor....") << std::endl; }
private:
int x;
int y;
short z;
};
int main()
{
Test* pa = new Test[10];
std::cout << std::format("pa Address {:#x} before delete\n", (INT_PTR)pa) << std::endl;
delete[] pa;
std::cout << std::format("pa Address {:#x} after delete\n", (INT_PTR)pa) << std::endl;
return 0;
}

new[]의 경우 `eh vector constructor iterator' 함수 내부에서 일어나는 일은 전부 알 수 없지만 건네준 인자를 활용해서 횟수만큼 생성자를 호출했다.
delete[]의 경우
객체의 시작 메모리 주소를 eax에 저장 후 임시 변수에 저장, nullcheck 후 vector deleting destructor를 호출하고 있다.

이렇게 건네준 포인터를 이용해 'vector deleting destructor' 함수 내부에서 객체들의 소멸자를 호출하는데
또 웃긴 것이 vector destructor itertor에서 소멸자를 호출하는 순서는 역순이라는 것이다.

[개수][객체1][객체2]...[객체N]
↑의 위치로 포인터를 이동 시킨 후 뒤로 가는 형태로
↑
↑
↑
↑
객체의 소멸자를 호출한다.
그 후 delete를 호출한다. 물론 delete[]도 마지막엔 free를 통해 메모리를 해제한다.
정리하면
- 기본 자료형에 대해서 new / delete 와 new[] / delete[]는 차이가 없음
- 클래스 객체에 대해서 new / delete 는 생성자 / 소멸자 호출
- 클래스 객체에 대해서 new[]를 하면
↓ (실제 malloc을 통해 할당 받음)
[개수][객체 시작]의 형태로 구성
↑ 사용자에게 전달하는 주소
이 되며, 개수만큼 생성자가 호출된다.
- delete[]를 하게 되면
↓ 사용자가 반납한 주소
[개수][객체 시작]
↑ 실제로 반납하는 주소
에서 앞의 개수를 참고해서 소멸자를 개수만큼 호출하게 되는데, 소멸자의 호출엔 this 포인터를 이용한다.
호출되는 소멸자의 순서는
[개수][obj1][obj2][obj3]...[objn] 이 존재한다고 했을 때
[obj n 소멸자]
...
[obj 3 소멸자]
[obj 2 소멸자]
[obj 1 소멸자]
의 순서로 호출이 된다.
new[] / delete[] 코드에 자신의 주소를 출력하도록 하고 실행해보았다.
std::cout << std::format("Test Destructor.... {:#x}", (INT_PTR)this) << std::endl

큰 주소부터 시작해서 작아지는 것을 확인할 수 있었다.
**- 소멸자 호출 (개수만큼)
- 멤버 변수에 접근할 때
- 접근하려는 주소가 commit된 페이지에 속한다면 소멸자 호출은 되지만 delete에서 heapFree 에러가 발생
- 접근하려는 주소가 commit이 아닌 상태(free 또는 reserve) 이라면 아예 멤버변수에 접근하려는 시도에서 access-violation이 발생
- 멤버 변수에 접근하지 않을 때
- 소멸자는 모두 호출이 되지만 delete에서 heapFree에러가 발생
**
class Test
{
public:
Test() : x(0x01234567), y(0x89abcdef),z(0xaabb) {}
~Test() { int a = 3; int b = a + 2; } // std::cout << x; //
private:
int x;
int y;
short z;
};
int main()
{
Test* pa = new Test[500];
// 여기서 [개수] 값을 임의로 변경
delete[] pa;
return 0;
}
실제로 다음과 같은 코드에서 pa앞의 개수를 임의로 변경하고 실행했을 때
멤버 변수에 접근하지 않는 소멸자 -> 에러 발생 x
멤버 변수에 접근하는 소멸자 (page commit 상태) -> 뒤에서부터 개수만큼 소멸자 호출이 가능하다면 소멸자가 개수만큼 호출, 에러 발생 x


- 멤버 변수에 접근하는 소멸자 (page free/reserve 상태) -> access violation 에러 발생

잘못 값을 바꿔도 에러가 발생하지 않는 부분이 있으니 저런 부분은 조심하자.
또 스택이랑 virtual함수들도 조금씩 정리해봐야겠다.. 시간이 지날수록 잊어버리니까 기록하는 습관을 들여야겠다.