-
[c++] delete[] 가 포인터를 받아도 잘 동작하는 이유(over-allocate)뜯고 또 뜯어보는 컴퓨터/씨쁠쁠 C++ 2022. 11. 9. 16:23반응형
c++에서는 동적으로 객체를 생성 및 삭제를 시켜줄 때, new 그리고 delete 키워드를 쓰게 됩니다. 이때, new는 우리가 메모리를 할당하고, 생성자를 호출하게 되어, 그 사이즈나 크기를 명시해주게 됩니다.
그렇지만 delete는 포인터만 넘겨줄 뿐인데, 어떻게 그 사이즈를 예측하고 삭제시켜 줄 수 있을까?
부연하자면, 밑과 같은 코드가 존재했을때, pFoo는 단순 포인터일 뿐인데, 어떻게 Foo array에 해당하는 메모리를 삭제시키고 알맞는 개수의 소멸자를 호출할 수 있을까요?
class Foo { public: Foo() {} ~Foo() {} int mNunber; } int main() { Foo* pFoo = new Foo[10]; delete[] pFoo; }
메모리 할당에 해당 2가지 방법론
이를 위해서 컴파일러는 2가지 방법론을 주로 사용하고 있습니다. 바로
- "over-allocation"
- "associative array"
라는 2가지 방법론을 구사하고 있습니다. 2가지중 어떤 것을 사용하는지 컴파일러 구현 방법에 따라 다르니 확인 바랍니다.
먼저 Over-Allocation에 대해서 설명해보도록 하겠습니다.
Over-Allocation - new
컴파일러가 Over-Allocation 기법을 사용하기로 했다면, p = new Foo [N]을 호출했을 때는 다음과 같은 테크닉을 쓰게 됩니다. 밑에서 설명하는 WORDSIZE의 경우, 컴파일러에 의존적인 상수인데요, 보통 sizeof(size_t)를 나타내게 됩니다. 32비트에서는 4, 64비트에서는 주로 8이 되는 것을 확인할 수 있습니다. (컴파일러 설계에 따라 다릅니다.)
그럼 코드 살펴보겠습니다. 우선 new에 해당하는 코드를 살펴보겠습니다.
// Original code: Foo* pFoo = new Foo[n]; char* tmp = (char*) operator new[] (WORDSIZE + n * sizeof(Foo)); // WORDSIZE 에 해당하는 메모리 할당 Foo* pFoo = (Foo*) (tmp + WORDSIZE); // WORDSIZE 만큼 이동후, Foo* p 라는 포인터 할당 *(size_t*)tmp = n; // tmp 에 n 을 대입, 즉 몇개의 객체가 있는지 확인 for (size_t i = 0; i < n; ++i) // pFoo + i 를 돌면서 객체 생성자 호출 new(pFoo + i) Foo();
- tmp에 WORDSIZE + 객체 배열의 사이즈에 해당하는 메모리 할당을 하고,
- WORDSIZE 만큼 이동후, Foo* p라는 포인터에 실제 배열 포인터를 할당합니다.
- tmp에 n을 대입하여 즉 몇 개의 객체가 있는지 확인할 수 있도록 합니다.
- 마지막으로 p + i를 돌면서 foo 객체 생성자 호출하게 됩니다.
다음과 같은 과정을 띄게 되는데, Over-Allocation의 경우, 배열 앞에 약간의 특정 메모리 공간을 할당하여, 배열의 객체가 몇 개 있는지 확인할 수 있도록 합니다.
반응형Over-Allocation - delete
그럼 이제 delete[] operator를 적용하게 되면, 어떤 식으로 작동되는지 확인해보겠습니다. delete[] 연산자를 적용하게 되면, 다음과 같이 컴파일러는 해석하게 됩니다.
// Original code: delete[] pFoo; size_t n = * (size_t*) ((char*)pFoo - WORDSIZE); // WORDSIZE 만큼 메모리 이동후, 사이즈 확인 while (n-- != 0) // N 만큼의 Foo 소멸자 호출 (pFoo + n)->~Foo(); operator delete[] ((char*)pFoo - WORDSIZE); // pFoo에서 WORDSIZE 만큼 뺀후, 메모리 해제
아까 new에서 over-allocation에 할당한 수를 통해, array의 개수를 파악할 수 있으며, 이를 통해 객체의 개수만큼 소멸자를 호출 후, 메모리를 해제해 주는 것을 볼 수 있습니다.
그럼 실제 코드를 컴파일 한 경과를 통해, 어셈블리를 분석한 것을 나타내고 보여드리겠습니다.
#include <iostream> const int n = 10; struct Foo { public: int b_var; ~Foo(){} }; int main() { Foo* bFoo = new Foo[n]; delete[] bFoo; }
위 코드를 어셈블리로 변환하면,
다음과 같이 Foo의 사이즈 * 10 해서, 40 에다가 + 8 만큼의 메모리를 더 할당하는 것을 볼 수 있습니다.
Over-Allocation - 정리
정리하자면, over-allocation의 경우, 배열의 메모리 할당 여부를 객체 앞에 위치시켜 얼마만큼의 메모리가 할당했는지 알 수 있도록 하는 테크닉이라고 정리할 수 있을 것입니다. 다음 화에서 설명드릴 "associative array technique"과 비교해서, over-allocation 테크닉은 속도가 빠르다고 할 수 있겠지만, 프로그래머가 delete [] 대신 delete를 사용했을 때, 오류가 발생할 수 있습니다.
긴 글 읽어주셔서 감사합니다.
도움이 되셨거나, 잘못된 내용이 있다면 댓글 부탁드리겠습니다.
반응형'뜯고 또 뜯어보는 컴퓨터 > 씨쁠쁠 C++' 카테고리의 다른 글
[C++] Reference 와 Pointer 의 차이점 설명해줄래 (0) 2022.11.14 [c++] 너 .. 혹시 생성자에서 가상함수 호출할거야 ? (2) 2022.11.09 [c++] New 그리고 malloc( ) 구분할 수 있어? (0) 2022.11.08 [객체의 관계] Is-A 그리고 Has-A 관계 ! (0) 2022.11.08 [c++] 상속을 어떻게 멈출 수 있을까 ? (0) 2022.11.06