본문 바로가기
C,C++

new 연산자 오버로딩

by violetoz 2013. 5. 13.

안녕하세요. 서버 개발자 이글입니다.

최근 갑작스럽게 인생이 꼬여버려 원치않은 백수로 전직.. 하염없이 애타는 마음으로 면접 결과만을 기다리던 중에..
(요즘 면접들은 경력이라도 2~4시간의 기술 필기 시험을 보더군요 -_-; 당황했습니다.)

바쁜게만 살아오다 한량이 된 갑작스러운 환경변화에 적응하지 못하고, 
허접하지만 지난 경험을 곱씹는 겸사겸사(아는 것도 생각이 안나더라는 ㅜㅜ), 
혹시라도 도움이 되실분이 있을거라는 기대에 글을 남깁니다.

미약하지만 게임코디에 조금이라도 보탬이.. 쿨럭..(굽신굽신)


경력자들이 보기에는 너무나 당연하고도 기초적인 내용일 수 있습니다.
제가 서버 개발 시 사용하던 스킬 중심으로 하나씩 정리해 보려구요.

어떤 것들은 너무 기초적인 것들이라고 욕하지 말아주시고, 이쁘게 봐주시길 :)

기본 of 기본!!!!

==============================================================
= 경험 많으신 개발자 분들이야 당연히 다 아시는 거니..              
= c++관련 책을 1번이상 정독한 초급 개발자분들을 대상으로 합니다 
==============================================================


c++개발자라면 당근 빠따롱 연산자 오버로딩을 많이 사용할 것입니다.

본 편에서는 일반적인 연산자 오버로딩과는 약간(?) 다른 new와 delete 연산자의 오버로딩을 응용하여
메모리 릭이 발생하는 서버 장애에 좀 더 유연하게 대처할 수 있는 방식을 기술 하고자 합니다.


============================================================================


프로그램을 개발하신 분들이 공통적으로 한번씩은 격는 장애가 있습니다.

바로 "메모리 릭

new 연산자는 우리에게 실행 시점에의 메모리 할당이 가능하게 하여 
필요한 시점에 os가 관리하는 메모리를 사용자 어플리케이션이 동적으로 사용할 수 있게 해주는 아주 고마운 녀석입니다.

하지만 잘못 사용하게 되면, 메모리가 줄줄줄... 
거기다가 새는 메모리가 일정이상 넘어가면 OS님하가 "이런! 위험한놈!" 이라고 하시면서 해당 프로세스를 죽여버리십니다.

만약 게임 서버와 같은 복잡한 프로그램에서 서비스 중에 간헐적으로 메모리 릭이 발생하게 되고, 재현이 어렵다면!?!?!?

프로그래머는 "도데체 어디서 새는거야!!" 라면서 머리를 쥐어 뜯으며, 소스를 이잡듯이 뒤지는 상황이 발생합니다.


타고난 능력자 님들을 제외하고는 이러한 경험을 한두번씩은 겪으신 분들이 많이 계실거라 생각합니다.
특히나 팀프로젝트 특성상 내가 아니더라고 신입개발자라던지.. 누군가는 만들어 낼 수도 있구요.
(나만 그랬나.................ㅠㅠ)


본 글에서는 메모리 릭 발생 시 어떤놈이 릭을 발생 시키는 주범인지 쉽게 유추가 가능하도록 도와주는 녀석을 만들고자 합니다.


본론으로 들어가서..



c++에서 new연산자의 역할은 무었일까요?

첫째로 가장 중요한 힙영역에의 실행 시점에서의 메모리 동적 할당이 있을 것이고,

둘째로 클래스의 경우 생성자(constructor) 의 호출로 객체 초기화를 해 준다는 것

셋째로 반환되는 주소 영역에 대한 형의 결정

다음 3가지가 있을 것입니다.(또 있나...ㅡㅡ;)


이때 우리가 하고자 하는 new 연산자에 대한 오버로딩은 첫번째 역할인 "메모리 공간에 대한 할당"만을 재정의 할 수 있습니다.

사용 법은 아래와 같이 매우 간단합니다.


·미리보기 | 소스복사·
  1. void* operator new(...){  
  2.   
  3. }  



그럼 이쯤에서 의문이 드실 수도 있을 겁니다.

"저걸로 메모리 릭을 어떻게 찾는다는 거지?"



자! 재정의가 가능하다는 것은 new가 호출 될 때 트리거링 되는 루틴을 만들수 있다는 것을 의미합니다.

한단계씩 진행 해 보겠습니다.


먼저 아래와 같이 동적으로 할당된 메모리에 대한 상세 정보를 저장할 struct를 정의 합니다. 
(본 글의 예제로는 struct를 사용하지만 입맞에 맞게 사용하시면 될 것 같습니다.)


·미리보기 | 소스복사·
  1. typedef struct tagMemAllocationInfo  
  2. {  
  3.       // new를 사용한 소스파일 명 편의상 char배열을 사용 
  4.       char   filename[MAX_FILE_NAME];     
  5.       int    line;         // new를 사용한 소스파일의 라인  
  6.       time_t allocate_time // 메모리 할당 시간  
  7.       void*  addr;          // 메모리 할당 주소  
  8. }MEM_INFO;  


그리고 요런식의 맵 타입을 하나 정의해 줍니다.
·미리보기 | 소스복사·
  1. // 프로세스의 메모리 동적 할당 된 정보를 저장하는 맵
  2. typedef std::map<void *, MEM_INFO>  MEN_INFO_MAP;       
  3. // 이얍! -ㅁ- 일단 개념만 설명하기 위해.. 귀차니즘으로 전역 선언
  4. static MEN_INFO_MAP CurMemAllocationInfoMap;
  5. static int CurMemAllocationCount = 0;

게임코디 소스편집기에 바로 짜고 있는 관계로.. STL의 사용 및 용법에 대한 부분은 이해 부탁드리구요..; 
일단 map을 사용한 이유는 delete호출 시 direct search를 하여 삭제해야 한다는 것을 어필하기 위함입니다.(성능)

물론 위의 맵과 카운팅 하는 부분은 DynamicMemoryManager 클래스로 만드시는게 좋겠지요? (귀차니즘...)


마지막으로 new와 delete연산자를 재정의 해줍니다.

아참 여기서 중요한 것은 첫번째 인자는 무조건 size_t가 들어가야 한다는 것!


·미리보기 | 소스복사·
  1. void* operator new(size_t sz, char* filename, int line)  
  2. {  
  3.       MEM_INFO meminfo;  
  4.       int len;  
  5.         
  6.       void* pfs = malloc(sz);  
  7.         
  8.       if(NULL == pfs)  
  9.       {  
  10.           cerr<<"[크리티컬]힙이 없네용"<<endl;   
  11.           exit(-1);  
  12.       }  
  13.   
  14.       meminfo.addr = pfs;  
  15.       meminfo.line = line;  
  16.       time(&meminfo.allocate_time);  
  17.   
  18.       len = strlen( filename );  
  19.       if( len > MAX_FILE_NAME )  
  20.       {  
  21.           cerr<<"[크리티컬] 헐 소스파일이름이 255가 넘어??"<<endl;  
  22.           exit(-1);  
  23.       }  
  24.   
  25.       strncpy(meminfo.filename, filename, strlen( filename )); 
  26.       //실무에서는 더 좋은 함수 쓰세요.. 
  27.     
  28.       CurMemAllocationInfoMap.insert(make_pair(pfs, meminfo));  
  29.       ++CurMemAllocationCount// 메모리 할당 수 증가  
  30.   
  31.       return pfs;          // 할당된 주소 반환  
  32. }  
  33.    
  34. void operator delete(void *p)  
  35. {  
  36.      // 맵에서 메모리 할당 정보 제거  
  37.      MEN_INFO_MAP::iterator it = CurMemAllocationInfoMap.find(p);  
  38.      ifCurMemAllocationInfoMap.end() == it )  
  39.      {  
  40.           cerr<< "메모리가 할당된 정보가 없는 주소에 대한 삭제 요청 주소는 : "<< p << endl;  
  41.           return;  
  42.      }  
  43.        
  44.      CurMemAllocationInfoMap.erase(it); // 맵에서 메모리 할당 정보 삭제  
  45.      --CurMemAllocationCount// 메모리 할당 수 감소  
  46.   
  47.      free(p); // 메모리 해제  
  48. }  
  49.   
  50. // 현재 메모리 할당 정보가 보고 싶은 시점에 콘솔에 찍어주던지.. 파일로 출력하던지.. EMS나 NMS로 전송하던지.. 입맛에 맞게 추가하시면 됩니당  
  51. void printCurMem()  
  52. {  
  53.      MEN_INFO_MAP::iterator it = CurMemAllocationInfoMap.begin();  
  54.      while( CurMemAllocationInfoMap.end() != it )  
  55.      {  
  56.           //TODO  
  57.           //여기에 입맞에 맞게 몇월 몇일 몇시에 [filename]소스파일의 [line]라인에서 [addr] 주소가 할당된 동적 메모리가 현재 남아 있다를 출력해주면 됨  
  58.   
  59.           ++it;  
  60.      }  
  61.   

new호출 할 때마다 파일명과 라인 넣기 귀찮으시다구요?

걱정 마세요. 우리에겐 매크로 함수가 있잖아요? :)

요렇게 하나 넣어줍니다.
·미리보기 | 소스복사·
  1. #define new new(__FILE__, __LINE__)       

그리고 일반적인 new 사용하듯이 사용하시면 됩니다.


아참 배열 할당도 정의해 주어야지요 :) 아래와 같은 예로 정의 하시고 입맛에 맞게 수정하여 사용하시면 되겠습니다.
·미리보기 | 소스복사·
  1. void *operator new[](size_t size, const char *filename, int line)   
  2. {  
  3.     return operator new(size, filename, line);  
  4. }  
  5.   
  6. void operator delete[](void *p)  
  7. {  
  8.     operator delete(p);  
  9. }  

오늘 다룬 new 및 delete를 오버로딩 하면 다음과 같은 장점이 있어요.

1. 예외에 강해짐.
   (프로그래머가 라이브러리 외부에서 잘못된 포인터를 사용하게 되어도 죽지 않으며, 무려 어느파일 어느라인에서 잘못되었는지 추정가능한 의미있는 데이터를 출력해줍니다!)
2. 메모리가 할당 된 시간을 알 수 있기 때문에 DB내의 데이터와 함께 사용하여, 대부분의 메모리 관련 원인을 찾아 낼 수 있음
3. 동적 메모리 할당 현황에 대한 통계를 프로그래머가 보고 싶은 시점에 언제든지 볼 수 있음


과도한 연산자 오버로딩을 싫어하지는 분도 많으니..
자기 스타일에 맞게 필요할 때! 적재적소에! 사용하시면 되겠습니다 :)


 
==================================================================================

알찬 강좌만 올라와 있는 게시판에 
사실 경력이라면 누구나 아는 내용의 허접한 내용을 적는 것이라.. 올릴까 말까 많이 고민했습니다만 ;) 
혹시라도 도움되실분이 있을지도 몰라 용기 내어 올려봅니다.

다음 내용은 C++의 캐스팅에 대해 허접하지만 나름 깊게 다루어 보려 합니다. 

코드는 IDE통하지 않고 글쓰기 코드 편집기에 바로 끄적인거라 빼먹은게 있을 수 있습니다..; 
잘못된 내용 지적해 주시면 급 수정 들어갑니다!!