new / delete

J·2025년 3월 23일

테스트

목록 보기
6/11
new
- 메모리 할당 및 생성자 호출
delete
- 소멸자 호출 및 메모리 할당 해제 ( 메모리 할당 해제 후 0x8123 )
- 포인터가 nullptr일 경우에 대한 처리가 되어있다. (null체크 후 return)
  • vs2022 사용중 ( x86, Release, 최적화 없음)

  • 기본 자료형 new / delete
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를 바로 하는 것을 확인할 수 있다.

new


delete



opertaor delete에서는 전달받은 메모리 주소를 free하는 코드가 존재했고, delete 후 컴파일러가 메모리주소에 0x8123을 넣었다. (앞으로 생략)


  • 기본 자료형 new[] / delete[]
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 하는 것은 다른 점이 크게 보이지 않는다.

new


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 / delete

new의 경우 call operator new를 통해 객체의 사이즈만큼 메모리를 할당받아온 후 null체크를 진행하고, 할당받은 주소를 토대로 생성자를 호출하는 것을 확인할 수 있다.


delete의 경우 scalar deleting destructor라는 함수에서 this포인터를 이용해 소멸자 호출과 delete함수를 호출한다.

delete내부에서는 free함수를 통해 사용된 메모리를 해제한다.




  • 객체 new[] / delete[]
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 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


큰 주소부터 시작해서 작아지는 것을 확인할 수 있었다.


  1. 그렇다면 new / delete[]?? new[] / delete 하면?

new/delete[] 의 경우

**- 소멸자 호출 (개수만큼)
    - 멤버 변수에 접근할 때
    	- 접근하려는 주소가 commit된 페이지에 속한다면 소멸자 호출은 되지만 delete에서 heapFree 에러가 발생
        - 접근하려는 주소가 commit이 아닌 상태(free 또는 reserve) 이라면 아예 멤버변수에 접근하려는 시도에서 access-violation이 발생
    - 멤버 변수에 접근하지 않을 때
    	- 소멸자는 모두 호출이 되지만 delete에서 heapFree에러가 발생
**

new[] / delete의 경우

  • delete 시 heapFree에러가 발생
    new : [개수][...]
    delete : 제대로된 포인터를 찌르고 있지 않음.

또한 new[] / delete[]에서 [개수] 부분이 조작되었을 경우

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함수들도 조금씩 정리해봐야겠다.. 시간이 지날수록 잊어버리니까 기록하는 습관을 들여야겠다.

  • 그리고 코드 내에서 cmp 0을 하는 부분을 nullptr 체크하는 부분으로 보인다
    또한 객체의 new[] / delete[]과정에서 개수가 원래보다 적은 쪽으로 조작되었다면 (new Test[100] => delete[] 시 적힌 개수가 100보다 작은 수로 조작되었다면 동작은 하지만 그만큼의 소멸자는 호출되지 않을 것이다.)
profile
낙서장

0개의 댓글