Coding Tip/C/C++

new 연산자를 사용한 동적 메모리 할당 실패시 예외 처리

장피디 2015. 6. 18. 16:10
반응형

new 실패에 따른 프로세스 종료원인을 찾다가 좋은 정보를 찾았습니다.

블로깅...


출처 : http://kuaaan.tistory.com/123




동적 메모리를 다음과 같이 사용하였습니다. 어디가 틀렸을까요?

  1. LPINT   lpInt = NULL;  
  2.   
  3. lpInt = (LPINT)malloc(sizeof(INT));  
  4. *lpInt = 4;  
  5.   
  6. printf("Value : %d\r\n", *lpInt);  
  7.   
  8. free(lpInt);  

동적 메모리를 할당한 후 정상적으로 할당되지 않은 경우에 대한 예외처리가 되지 않았군요. ^^
좀더 꼼꼼한 분이라면 다음과 같이 코딩하시겠지요.
  1. LPINT   lpInt = NULL;  
  2.   
  3. lpInt = (LPINT)malloc(sizeof(INT));  
  4.   
  5. if (lpInt == NULL)  
  6. {  
  7.     printf("Fail to alloc memory!!\r\n");  
  8.     return FALSE;  
  9. }  
  10.   
  11. *lpInt = 4;  
  12.   
  13. printf("Value : %d\r\n", *lpInt);  
  14.   
  15. free(lpInt);  
위와 같이 코딩하면 메모리 할당에 실패한 경우 "Fail to alloc memory!!"라는 에러메시지를 표시하고 프로그램은 종료하게 됩니다.


그렇다면 malloc 대신 new를 사용하는 다음의 코드는 어떨까요?
많은 분들이 사용하시는 코드입니다만... 이렇게 하면 동적 메모리 할당이 실패한 경우에 대해 올바르게 Exception Handling이 가능할까요?
  1. LPINT   lpInt = NULL;  
  2. INT nTotalBytes = 0, nAllocByte = 10000000;  
  3.   
  4. while(TRUE)  
  5. {         
  6.     lpInt = NULL;  
  7.   
  8.     lpInt = new INT[nAllocByte];  
  9.     nTotalBytes += nAllocByte * sizeof(INT);  
  10.   
  11.     if (lpInt == NULL)  
  12.     {  
  13.         printf("Fail to alloc memory!!\r\n");  
  14.         break;  
  15.     }  
  16.   
  17.     printf("Alloc Success!! (%d)\r\n", nTotalBytes);  
  18. }  
  19.   
  20. // blah~ blah~  

결론부터 얘기하자면 위와 같이 포인터가 NULL인지 체크하는 방식으로는 new 연산자에 대한 예외처리를 할 수 없습니다.


위와 같이 프로그램은 에러창을 내뱉고 죽어버립니다. 죽는 부분에도 보면 "Fail to alloc memory!!" 라는 메시지는 보이지 않습니다. 말하자면, 예외 처리 루틴을 타지 않은 것이지요.

new 연산자 관련된 MSDN 을 검색해보면 다음과 같은 내용이 있습니다.

말하자면, new 연산자가 메모리 할당에 실패했을 경우, C++ 스탠다드에 따라 std::bad_alloc Exception을 throw한다는 의미입니다. exception이 throw되었기 때문에 넘겨받은 포인터가 NULL인지 체크하는 부분까지 진행되지도 않고 프로그램이 종료된 것입니다.



음. 그렇다면 다음과 같이 코딩하면 메모리 할당 예외처리가 가능하겠군요.
  1. try  
  2. {  
  3.     LPINT   lpInt = NULL;  
  4.     INT nTotalBytes = 0, nAllocByte = 10000000;  
  5.   
  6.     while(TRUE)  
  7.     {         
  8.         lpInt = NULL;  
  9.   
  10.         lpInt = new INT[nAllocByte];  
  11.         nTotalBytes += nAllocByte * sizeof(INT);  
  12.   
  13.         printf("Alloc Success!! (%d)\r\n", nTotalBytes);  
  14.     }  
  15. }  
  16. catch ( exception e )  
  17. {  
  18.     printf("%s\r\n", e.what( ));  
  19. }  

코드를 위와 같이 수정하고 실행시키면 다음과 같이 동작하게 됩니다.

종료되기 직전 부분을 보면 제대로 Exception Handling이 되었다는 것을 알 수 있습니다.

그런데, 동적메모리 할당할 때마다 이와 같이 try ~ catch를 해주려면 너무 귀찮죠. 성능 문제로 try ~ catch를 사용하시지 않는 경우도 있구요. 더 좋은 방법은 "new handler"를 등록해주는 것입니다.


MSDN을 좀 더 검색해보면 "new handler"에 관한 내용이 나와 있습니다.

If the attempt is successful, the function returns a pointer to the allocated storage. Otherwise, the function calls the designated new handler. If the called function returns, the loop repeats. The loop terminates when an attempt to allocate the requested storage is successful or when a called function does not return.

The required behavior of a new handler is to perform one of the following operations:

  • Make more storage available for allocation and then return.

  • Call either abort or exit(int).

  • Throw an object of type bad_alloc.

The default behavior of a new handler is to throw an object of type bad_alloc. A null pointer designates the default new handler.

The order and contiguity of storage allocated by successive calls to operator new(size_t) is unspecified, as are the initial values stored there.


위의 내용을 요약해보면 다음과 같습니다.

1. new 연산자가 메모리 할당에 실패한 경우 "new handler"로 지정된 함수가 호출된다. 이 함수가 리턴한 다음에 메모리 할당이 실패한 시점부터 계속 실행된다.

2. "new handler" 는 다음의 세가지 작업 중 한가지를 수행해야 합니다.
1) 메모리를 확보한 후 return한다.
2) 프로세스를 종료시킨다.
3) bad_alloc 을 throw한다.

3. Default "New Handler"는 bad_alloc 을 throw 한다.

음, new 연산자가 메모리 할당에 실패했을 때 bad_alloc이라는 exception이 throw 되는데, 이것은 사실은 Default로 등록된 "new handler"에서 하는 일이라는 것을 알 수 있네요.

그렇다면, 이 내가 원하는 함수를 "new handler"로 등록하려면 어떻게 해야 할까요??
이런 용도로 사용되는 set_new_handler 라는 API가 있습니다.

set_new_handler 를 사용해 다음과 같이 new handler를 지정할 수 있습니다.
  1. void __cdecl MyNewHandler( )  
  2. {  
  3.    printf("Fail to Alloc Memory \r\n");  
  4. //   throw bad_alloc();  
  5.    ExitProcess(FALSE);  
  6.    return;  
  7. }  
  8.   
  9. int _tmain()  
  10. {  
  11.     set_new_handler (MyNewHandler);  
  12.   
  13.     LPINT   lpInt = NULL;  
  14.     INT nTotalBytes = 0, nAllocByte = 10000000;  
  15.   
  16.     while(TRUE)  
  17.     {         
  18.         lpInt = NULL;  
  19.       
  20.         lpInt = new INT[nAllocByte];  
  21.         nTotalBytes += nAllocByte * sizeof(INT);  
  22.   
  23.         printf("Alloc Success!! (%d)\r\n", nTotalBytes);  
  24.     }  
  25.   
  26.     // blah~ blah~  
  27. }  

결과는 다음과 같습니다.


제대로 Handler 가 호출되었다는 것을 알 수 있습니다.

그렇다면 이 Handler에서는 어떤 작업을 해주어야 할까요?

이 Handler가 return되면 실패했던 new 작업을 재시도합니다. 이때 만약 다시 실패한다면 new handler가 다시 호출되지요. 따라서 new handler에서 메모리부족을 해결하지 않고 return해버린다면 무한루프에 빠지게 됩니다.

그렇다면 new handler에서 메모리를 확보하는 것이 가능한가? 사실은 불가능합니다. 메모리가 부족하다는 것은 실제로 시스템 가상메모리가 부족하거나 아니면 Application에서 Memory Leak이 발생하고 있다는 의미가 되는데, 일개 Exception Handler에서 맘대로 시스템 가상메모리를 확보할 수도 없고, Leak된 메모리를 쫓아가서 메모리해제를 해 줄 수도 없는 노릇이죠. (만약 그게 가능하다면 처음부터 Leak이 발생하지도 않았겠죠. ^^)

제 생각에는 이 예외처리자는 그냥 로깅을 남기고 예외창 없이 조용히 죽게 만드는 정도의 작업 외에는 실제로 할 수 있는 일은 없을 것 같습니다.
혹시 다른 생각이 있으신 분은 알려주세요. :)

※ 이 글에서 쓰인 샘플코드는 MSDN의 코드를 참고하였습니다. :)


-----------------------------------------------------------------------------------------------------------------
출처 : http://cafe.naver.com/totalprogramming/34

보통 C++ 개발자중에 상당수가 C언어를 접했을 것이다.

간혹가다보면^^ C++개발자들이 실수를 하는데,

바로 new연산자를 사용함에 있다.

 

char* buf;

buf = new char[100];

if(NULL == buf){

      return FALSE;

}

 

이런식으로 사용하고 예외 처리를 하는데, 여기에는 문제점이 있다.

new연산자는 heap 메모리가 부족할 경우에는 NULL을 리턴하지 않는다.

할당을 실패할 경우에는 예외가 발생되어, if(NULL == buf)이 구문이 수행되지 않는다.

NULL체크 방식은 과거의 malloc을 사용했을 경우의 처리인 것이다.

C++에서는 이런 예외처리를 하기 위해 아래와 같이 #include <new>에 있는 bad_alloc객체를 이용해서

예외처리를 하고, NULL체크를 할수 있는 방식도 제공해준다.

 

 

#include <new>
#include <iostream>
using namespace std;

class BigClass {
public:
   BigClass() {};
   ~BigClass(){}

#ifdef _WIN64
      double BigArray[0x0fffffff];
#else
      double BigArray[0x0fffffff];
#endif
};

int main() {
   try {
      BigClass * p = new BigClass;
   }

   catch( bad_alloc a) {
      const char * temp = a.what();
      cout << temp << endl;
      cout << "Threw a bad_alloc exception" << endl;
   }

   BigClass * q = new(nothrow) BigClass;

   if ( q == NULL )
      cout << "Returned a NULL pointer" << endl;

   try {
      BigClass * r[3] = {new BigClass, new BigClass, new BigClass};
   }

   catch( bad_alloc a) {
      const char * temp = a.what();
      cout << temp << endl;
      cout << "Threw a bad_alloc exception" << endl;
   }
}



반응형