본문 바로가기
컴퓨터/디버깅

DC를 제대로 해제하지 않아서 생기는 문제

by adnoctum 2010. 5. 19.


다음과 같은 에러가 났다.

환경 : Visual C++ 9.0 (VS2008) on Windows XP SP2



디버깅 모드로 실행시키고 있는 중[각주:1]이었기 때문에 위에서 다시 시도(R) 버튼을 눌러서 디버깅을 시작한다. 그러면 다음과 같이 breakpoint 를 trigger 했다는 메세지가 나오는데, 여기서 Break 를 누른다. 그러면 실제로 에러가 난 코드에서 멈추게 된다.



실제로 에러가 난 줄까지 어떤 호출경로를 통해서 들어갔는지 살펴 보기 위하여 Call Stack 을 살펴 본다. 지금의 경우는 다음과 같다.


보면은,

IICGPerfusion2::save_map_information(~) --> CImage::~CImage() --> CImage::Destroy() --> CImage::Detach()

순으로 호출이 일어나고 있다. 즉, IICGPerfusion2::save_map_information 함수에서 CImage를 사용했고, 그것이 파괴되면서 파괴자를 부르는데, CImage의 파괴자에서 resource를 깨끗이 삭제하는 과정에서 Destroy를 호출했고, 이 Destroy 에서 Detach 를 호출했는데, CImage::Detach 함수 안에서 문제가 생겼다. Debug Assertion Failed 대화 상자(제일 처음 그림)에 나와 있는 것과 같이 실패한 구문은

ATLASSUME( m_hDC == NULL );


인데, 현재 파괴자를 부르고 있는 CImage의 m_hDC 가 NULL 이어야 한다는 의미이다. 그렇다면 왜 NULL 이 아니었는지를 살펴 보기 위하여 IICGPerfusion2::save_map_information 함수에서 에러가 난 부분으로 가보면[각주:2],


위와 같다. 현재의 위치를 나타내는 마크가 1010 번째 줄에 나타난 것을 볼 수 있다(가장 왼쪽 아래 녹색 화살표). 언뜻 이해할 수 없는 것은 현재 실행시키려는 구문이


 1010 pDC->TextOut(3, height+20, _T("0"));

으로, 딱히 CImage를 파괴하는 것으로 보이지 않는다. 이렇게 에러가 난 구문이 에러의 내용과 직접적으로 연관되는 것으로 보이지 않는 경우 그 앞/뒤를 전반적으로 두루 살펴 보아야 한다.

위의 경우, 녹색 화살표로 표시된 것은 아직 1010 번째 줄을 실행시키지 않았다는 의미인데, 그렇다면 바로 그 직전에 } 를 닫으면서 문제가 생긴 것으로 생각할 수 있다.

  993 if(_map_type == 0){ // perfusion map

  994     colorbar.Draw(image.GetDC(), 0, height, width, 20, 0, 0, colorbar.GetWidth(), colorbar.GetHeight());        

  995 }

  996 else if(_map_type == 1){ // Tmax map

  997     CImage temp;

  998     temp.Create(width, 20, 24);

  999     colorbar.Draw(temp.GetDC(), 0, 0, width, 20, 0, 0, colorbar.GetWidth(), colorbar.GetHeight());

 1000     // gradient 를 거꾸로 그림.

 1001 

 1002     for(x = 0; x<width; x++){

 1003         COLORREF cr = temp.GetPixel(x, 1);

 1004         for(y = 0; y<20; y++){

 1005             image.SetPixel(width-x-1, height + y, cr);

 1006         }

 1007     }

 1008 }


그 위 부분을 살펴 보면 [_map_type]이 1로 되어 있어서 else if 구문 안쪽을 실행하고 있으므로 에러는 997~1007 번째 줄 중간 과정에 있을 것이다. 실제로 997 번째 줄에 break point 를 걸어서 따라가 보면




위에서처럼 1008 번째 줄을 실행하기 전까지 에러가 나지 않는다. 그러나 1008 번째 줄을 실행하면서 제일 위의 에러 창이 뜬다. 이것은 무엇을 의미하는 것일까? 이것은 닫는 } 의 의미와 ASSERT 구문, 그리고 call stack 에서 보여준 함수 호출 순서를 생각해 보면 알 수 있게 된다.

C/C++ 에서 닫히는 중괄호 } 는 stack 이 파괴됨을 의미한다. 아무 것도 아닌 것처럼 보이는 { } 는 새로운 스택 영역을 만드는 것이기 때문에, 여는 중괄호 { 에 의해 새로운 스택 영역이 만들어지고, 닫히는 중괄호 } 에 의해 그 영역이 파괴된다. 함수도 그렇게 생각할 수 있다. 즉, 함수 내부에서 선언된 변수들이 함수 밖에서 보이지 않는 것은, 함수 역시

return_type function_name(parameters...)
{ <-- 함수가 사용할 stack 을 만들기 시작하는 중괄호
      // 함수 내용
} <-- 함수가 사용했던 stack 이 파괴되는 것을 알리는 중괄호

위처럼 같은 선상에서 이해할 수 있다. 난 가끔 귀찮으면 변수 재정의 때문에

{
   // 코드 구문 1
}
{
   // 코드 구문 1
}


처럼 for 문을 흉내내곤 한다. 위의 경우 코드 구문 1의 완전히 똑같은 코드가 두 번 실행이 되면서 새로운 변수들이 똑같이 선언되어도 { } 로 각각의 영역에 대한 stack 을 만들었다 파괴하기 때문에 변수 재정의 같은 에러가 나지 않는다[각주:3].

다시 에러 내용으로 돌아 가서, 1008 번째 줄의, 닫히는 중괄호 } 를 실행 전까지 에러가 없다가 이 줄을 지나면서 에러가 났다는 것은 그 중괄호가 닫히면서 파괴되는 stack 영역에 있던 변수가 제대로 지워지지 않았기 때문일 것이다. 즉, 1008 번째 줄의 닫히는 중괄호를 처리하면서, 996 번째 줄에서 열리는 중괄호 { 에 의해 만들어진 stack 영역 안에서 만들어진 변수들이 제대로 지워지지 않고 있는 것이다. 996 번째 줄의 else if 구문이 시작되면서 만들어진 새로운 변수는 CImage temp 밖에 없다. 그렇다, ASSERT에 의해 중단되었던 이유는 이 CImage 형 변수 temp 가 삭제되면서 문제가 생겼던 것이다. 이것은 call stack 에서 보이는 함수 호출 순서와도 정확히 일치한다.

그렇다면 왜 temp 가 파괴되면서 temp.Detach() 를 호출할 때,

ATLASSUME( m_hDC == NULL );

에서 걸렸을까? 즉, 왜 temp는 아직 [m_hDC]가 NULL 이 아닐까? 그것은, 즉 temp가 얻은(get) dc를 반환하지 않았기 때문이다. 다시 에러가 난 부분 근처를 살펴 보면,

  colorbar.Draw(temp.GetDC(), 0, 0, width, 20, 0, 0, colorbar.GetWidth(), colorbar.GetHeight());


부분이 있고, 그 안에 temp.GetDC() 를 하는 것이 보인다. 따라서 CImage 형 변수 temp는 이 부분에서 DC 를 얻었다. 이렇게 얻은 DC는 temp 가 파괴되기 전에 해제(release)를 해주는 것이 좋다. 그런데 현재, temp 를 포함하는 가장 작은 stack 이 파괴되기 전까지 temp에서 DC를 release 해주지 않았기 때문에 위와 같은 에러가 났던 것이다.

해결책은 역시나 간단한데, temp가 더이상 필요 없는 부분에서 temp.ReleaseDC() 를 해주면 된다. ㅋ

그런데 왜 ASSERT 에 걸리게 된 것일까? (참고 : Debug Assertion Failed!) ASSERT는 디버그 모드로 실행하지 않으면 실행되지 않는 구문이며, 이 경우 위 코드를 수정하지 않고 릴리즈 모드로 실행하면, 역시나

ATLASSUME( m_hDC == NULL );

구문이 실행되지 않기 때문에 위와 같은 에러 메세지가 뜨지 않는다. 즉, CImage형 변수 temp의 DC를 release 해주지 않는 것은 그 당장은 문제가 되지 않기 때문에 릴리즈 모드에선 에러가 나지 않게 됨과 동시에 ASSERT로만 체크하게 되어 있는 것으로 보인다. 그래서 내가 오래 전에 해 본 것이 있지. ㅋㅋㅋ ReleaseDC 를 해주지 않고 수백만 번 함수를 호출해 보았더니, 결국은 프로그램이 멈추어 버렸다. pixel 단위로 처리하는 경우였고, 각 pixel을 다룰 때마다 DC 를 얻기만 하고 해제를 하지 않았더니 결국 프로그램이 멈추어 버렸다. 그 이후 난 항상 DC 를 해제하게 되었다.

으... 이 클래스 자체가, 이름도 그렇고, 좀 테스트 하는 성격이 강해서 조금 대충 코딩했더니 저런 경우가 생긴다...
  1. Debug Mode로 설정한 후 F5 로 실행시킨 경우. [본문으로]
  2. Call Stack 윈도우에서, 내가 작성하는 부분이 처음 나오는 부분을 더블 클릭하면 그 화면으로 가게 된다. [본문으로]
  3. 물론 위처럼 사용하는 것은 극히 일부의 경우에 임시로 사용할 뿐이다. 여기서는 { } 의 의미를 좀 더 명확히 말하고자 사용하였다. [본문으로]

'컴퓨터 > 디버깅' 카테고리의 다른 글

no matching function for call to  (0) 2010.05.24
for 문의 동작 순서  (1) 2010.05.19
too few arguments to function  (0) 2010.05.18
stl_algo.h:2280: error: assignment of read-only location  (0) 2010.05.18
undefined reference to  (0) 2010.05.18