c++에서 흔히 사용하는 new, 그리고 delete.
그안에 감춰진 비밀을 파해쳐 보자! 팍팍!!(...)
std::string* ps = new std::string( "Memory~" );
delete ps;
다음은 흔히들 사용하는 new와 delete의 사용 예중 하나이다.
먼저 new 부터 확인해 보자.
new, 보통 포인터에 메모리를 할당하는 상황에서 사용되는데,
이 연산자는 두단계를 거쳐서 일을 수행한다.
먼저, 요청한 타입의 객체를 담을 수 있는 크기의 메모리를 할당.
그 다음 생성자를 호출하여 객체 초기화를 수행하는 것이다.
이 두단계는 바꿀 수가 없다. 당연한 이치다.
여기 까지 보면, "이게 뭐?" 그래서 어쩌란건데..? 라는 의문이 들지도 모르겠지만. 중요한건 지금부터.
new가 하는 첫번째 일
타입의 객체를 담을 수 있는 메모리 할당.
이를 operator new 라고 하며,
우리는 그 operator new를 재정의 할 수 있다고 한다.
new 연산자 자체를 오버로딩하는 것은 "불가능" 하지만.
메모리를 할당하는 operator new에 대해서는 원하는 방식으로 수정할 수 있다는 것이다.
(근데 별로 하고 싶진 않네...)
일단 이번 항목 자체는 operator new를 오버로딩 하는 방법이 아니다.
조금더 보자. operator new 함수는 다음과 같이 선언된다.
void* operator new( size_t size );
이 함수는 void* 타입으로 반환을 하게 되는데,
초기화 되지 않은 원시 메모리의 포인터를 반환하기 때문이다.
operator new 를 오버로딩 할 때 추가적인 매개변수를 넣게 할 수도 있다는데,
이 때에 주의점은 첫번째 매개변수는 무조건 size_t size를 해야 한다는 것이다.
new를 사용하지 않고, 단순히 메모리 할당만 하고 싶다.
이럴 경우에는 그냥 편하게.
void* rewMemory = operator new( sizeof(string) );
이처럼 해주면 된다. 이렇게 하면,
string 타입의 객체를 담을 수 있는 메모리 덩어리를 할당해서 그 메모리 포인터를 반환해 준다고 한다.
좀 쉽게 설명하면 operator new 는 c의 malloc과 마찬가지로 메모리만 할당하는 것뿐이라고 한다.
즉, 다음과 같이 new 연산자를 호출 하면,
// new 호출!
std::string* ps = new std::string( "Memory~" );
// new 안에서는
// 먼저 operator new를 호출하여 메모리를 할당하고
void* memory = operator new( sizeof(std::string) );
// *memory에 대해
// string::string("Memory~")를 호출.
// 메모리 값을 세팅(생성자대로) 객체를 초기화한다.
// 마지막으로
// ps 가 memory를 가르키게 한다.
std::string* ps = static_cast<string*>(memory);
이 부분에서 memory에 값을 세팅, 객체를 초기화 하는 부분은.
우리가 어찌 할 수 없는 부분이라고 하니,
뭘 어떻게 조작해 볼 생각따윈 집어 치우고, 저런 방식으로 new가 진행되는구나~
하고 넘어가는 것이 좋을 듯 하다.
즉, 힙에 할당하는 용도에 우리가 쓸 수 있는 수단은 new 연산자 뿐이라는 거다.
음. 뭔가 설명이 부족한거 같은데, 어찌 보면
결국 new로 할당하라는거잖아? 라고 볼 수 있겠지만,
꼭 그렇진 않다. 지금까지의 이야기는 쉽게 말해 new하여 메모리를 할당하였을 때,
호출 되는 순서, 라고 볼 수도 있다. 그 첫번째에 operator new가 있다는 것,
그리고 operator new는 malloc 처럼 메모리를 "할당"하는 일을 맡고 있다는 것.
일단 그것을 파악하고 넘어가는 것이 가장 중요하다고 생각한다.
이쯤에서 나오는 단어가. 메모리 지정 new(Placement new)라는 것인데,
나도 읽으면서 몇번을 해깔려서 자꾸 보게 되는데,
잘 설명할 수 있을지는...
자. 코드를 보자.
class Widget
{
public:
Widget( int widgetSize ){};
};
// 이 함수는 Widget 객체를 buffer로 지정되는 메모리에 생성한 후
// 그 포인터( buffer와 같은 값)를 반환한다.
// 즉, 공유 메모리. 라고 보면 편하겠다.
// buffer라는 메모리에다가 생성했다. 정도로 보면 맞는 말일려나..?
Widget* constructWigetInBuffer( void* buffer, int widgetSize )
{
return new (buffer) Widget( widgetSize );
}
이게 공유 메모리나 메모리-맵 I/O를 사용하는 어플리케이션을 만들 때 꽤 유용하다는데...
객체를 특별한 주소 공간이나 별도의 루틴을 통해 할당한 메모리에다가 두어야 하기 때문이란다.
그러니까, 이미 할당되어 있는 메모리 공간, 아직 뭔가 들어가 있지 않은 빈 방에다가.
내가 원하는 것들을 그곳에다가 채워 넣고 싶을 때 이런식으로 사용한다곤 하는데.
아직 그런 경험이 없어서, 어떨때 어떤식으로 사용해야 하는진 잘 모르겠다.
가끔 이런 소리 나오면, 가슴속으로. "아.. 이게 뭔 개소리야." 라고 생각하는걸 보면.
난 진짜 슈퍼 개 잉여인가. 싶기도 한데.
여튼, 다시 본론으로 들어가서.
저런식으로 new를 호출하게 되면,
void* operator new( size_t, void* location )
{
return location;
}
이처럼 operator new가 호출이 된다고 한다.
operator new가 메모리 할당이니까,
저런식으로 내가 원하는 공간을 메모리 할당 지역으로 쓰겠다.
라고 보면 될 것 같다.
메모리 지정 new를 사용 하려고 했을 경우.
#include <new>
를 지정해 주면 된다고 한다.
참 간단하다고 한다.
"뭐!? 임마?"
사실 좀 복잡하게 설명하긴 했지만, 개념만 따지면 뭐, 그렇게 어렵진 않은거 같긴한다. 정말로.
new 를 쓴다.
메모리를 할당한다.
객체를 생성하고 초기화 한다.
operator new를 쓴다.
메모리만 할당한다.
끝.
이처럼 new와 operator new는 분명 다르다.
구지 수식을 표현한다면,
new >(포함) operator new 가 될려나?
어째뜬 new를 사용하게 되면 operator new를 거치기 때문에.
operator new를 수정,
new를 사용하면 new를 사용 할 때, 내가 원하는 공간을 배치하는 것이 가능하다는 것이다.
operator new를 봤으니.
이제 delete를 보자.
지금까지 이해를 했다면,(내 설명이 후져서 이해를 했을진 모르겠다.)
delete가 어떻게 돌아가냐고 물었을 때,
얼추 예상은 할 수 있을 것이다.
new와 반대.
ps->~std::string(); // 소멸자 호출
operator delete(ps); // 객체가 자리잡은 메모리 해제
처럼 진행될 것이다.
만약 사용자가 operator new를 통해 메모리를 생성했다면,
operator delete 를 사용해 메모리를 해제하여야 할 것이다.
// 50개의 문자를 담을 수 있는 메모리 할당
// 생성자는 호출 하지 않는다.
void* buffer = operator new( 50 * sizeof(char) );
// 메모리를 해제한다.
// 소멸자는 호출 하지 않는다.
operator delete( buffer );
이처럼 말이다.
조금더 이야기 하자면,
메모리 지정 new를 사용해. 생성하였다면
delete로 지우면 안될 것이다.
delete 연산자는 operator delete 함수를 호출하여 메모리를 해제하게 되는데,
그 객체를 담고 있는 메모리는 operator new로 할당된 것이 아니라. 사용자가 직접 정한 어딘가에 할당하였기 때문이다.
나도 자꾸 해깔리는데, operator new와 메모리 지정 new는 다르다.
new를 사용하면 operator new를 거쳐가는 것이고.
메모리 지정 new는 기존의 operator new가 아닌 내가 원하는 어떤 공간에 넣은 것이기 때문에.
일반적인 delete를 사용하면 그게 어딘지 모른다는거다.
음. 다음 코드를 보면 대강 감이 올 것이라고 생각한다.
// 공유 메모리에 메모리를 할당하고 해제하는 함수
void* mallocShared( size_t size ); // 할당하는 놈이라고 치고
void freeShared( void* memory ); // 해제하는 놈이라고 치자.
void* sharedMemory = mallocShared( sizeof(Widget) );
int main()
{
// 메모리 지정 new를 사용.
Widget* pw = constructWidgetInBuffer( sharedMemory, 10 );
delete pw;
// 안됨. sharedMemory는 operator new로 만든게 아니라.
// mallocShared로 만든녀석이니까.
pw->~Widget(); // 문제는 없다.
// 소멸자를 호출할 뿐, 메모리를 해제 하는것은 아니기 때문에.
freeShared(pw); // 문제 없다.
// pw가 가르킨 메모리를 해제
// 소멸자는 호출하지 않는다.
return 0;
}
복잡하네.
이쯤에서 좀 정리가 필요 한거 같은데,
new를 사용하면,
operator new를 사용하여 메모리를 할당하고.
그 메모리에 생성자, 초기화 한다.
delete를 사용하면
그 메모리에 소멸자에 들른 후,
메모리를 삭제한다.
operator new만 사용하게 되면,
메모리를 할당만 할 수 있고.
operator delete를 통해 삭제만 할 수 있다.
메모리 지정 new를 사용하면,
이미 할당되어 있는 공간에 내가 원하는 녀석으로 생성, 초기화 할 수 있으며,
그렇게 하였을 경우에는,
꼭 그 녀석을 삭제하는 부분을 따로 만들어서 삭제,
delete로 마냥 삭제 해서는 안된다.
정도로 정리 할 수 있을 것 같다.
ps : 만약 내가 생각한 것이 틀리다면, 꼭 틀리다고 해주세요. 제발;;
자 어째뜬 마지막으로.
그렇다면 이녀석들이 단일이 아니라. 배열이라면??
그것은 그것대로 따로 조정을 해 주어야 한다고 한다.
즉,
std::string* ps = new std::string[10];
이와 같은 경우는.
operator new가 아니라.
operator new [] 가 호출 된다고 한다.
마찬가지로 operator delete가 아
닌 operator delete [] 가 호출 되는건 당연지사.
operator delete[]의 경우엔 오버로딩 하기 위해선,
제한이 존재 한다는데, 책에서는 언급되어 있지 않다.
뭐, 대충 여기 까지 인데,
new와 delete는 어찌 손 볼 수 없다.
하지만 내부적으로 메모리 할당, 해제는 마음만 먹으면 어느정도 손 볼수 있다는게 책에서 내리는 결론.
"하는 일을 수행하는 방법"은 고칠 수 있을 지 몰라도.
"하는 일" 자체를 바꿀 수 없다는 것이다.
ps2 : 복잡하네 정말.
정말 이런걸 다 생각하면서 코딩하는 사람이 있기는 한거냐?? 젠장.
여튼 항목8은 좀 개념적으로 복잡하네
어찌 보면 쉬운거 같기도 한데,
처음 보면 뭔 소린가 싶기도 할거 같고.
벌써 새벽 5시 반이넘었네.
시티 헌터나 보고 자야지.징징
'프로그래밍 > More Effective C++' 카테고리의 다른 글
항목9. 리소스 누수를 피하는 방법의 정공은 소멸자이다. (0) | 2011.07.06 |
---|---|
항목7. &&, ||, 혹은 , 연산자는 오버로딩 대상이 절대로 아니다. (0) | 2011.06.30 |
항목6. 증가 및 감소 연산자의 전위 / 후위 형태를 반드시 구분하자. (0) | 2011.04.29 |
항목5. 사용자 정의 타입변환 함수에 대한 주의를 놓지 말자 (0) | 2011.04.28 |
항목4. 쓸데 없는 기본 생성자는 그냥 두지 말자. (1) | 2011.04.25 |