본문 바로가기
Program/C++ MFC

[C++ MFC] OnCtlColor 메모리 누수 확인 방법 및 조치

by 냠만 2024. 6. 27.

[OnCtlColor 함수란]

MFC 프로그램을 하다 보면 화면 구성을 조금 더 깔끔하고 간략하면서 직관적으로 구성하기 위해 노력을 많이 하게 됩니다. 그러다 보면 꼭 마주칠 수밖에 없는 메시지 함수가 바로 OnCtlColor함수입니다.

 

OnCtlColor함수는 시스템 메시지를 이용해서 컨트롤의 속성을 변경해 주는 함수입니다. 해당 부분에서 컨트롤의 nID (Resource.h 참조)를 가져와서 해당 컨트롤의 속성값을 변경할 수 있는 겁니다. 여기서 변경되는 색상 관련 속성값은 리소스뷰에서 작업할 수 없어 별도로 코드를 구성해야 합니다.

 

그런데 OnCtlColor를 사용할 때 매우 중요하게 고려해야 하는 부분이 있습니다. 이 함수를 잘못 사용하게 되면 치명적인 메모리 누수가 발생되어 프로그램이 종료되어도 메모리 해제가 되지 않고 시스템에 치명적인 기능손실을 유발하게 됩니다. 재부팅하기 전까지는 메모리를 잡고 있기 때문에 재부팅만이 해결할 수 있는 방법이 되어버립니다.

(비슷한 케이스로 OnPaint 함수도 조심해야 합니다.)

 

그렇다면 프로그램에 이상이 생기기 전에 메모리누수가 있는지 확인할 수 있는 방법은 어떤 게 있는지 보겠습니다.

[메모리 누수 확인 방법]

작업관리자 세부 정보 접근

본 문서는 Window11 기준으로 설명하나, Window10도 비슷합니다. 먼저 작업관리자에 진입해야 합니다. 키보드 Ctrl + Shift + ESC 또는 Ctrl + Alt + Delete 버튼을 눌러 진입할 수 있습니다.

이후 작업관리자 옆쪽에 보시면 세부 정보에 진입할 수 있습니다.

열 선택 접근

세부 정보에 진입 후 저희가 봐야 될 항목은 GDI 개체인데 일반적으로 가려져 있습니다. 항목에서 우클릭을 해서 열 선택으로 진입합니다.

GDI 개체 선택

아래로 조금만 내리다 보면 GDI개체를 발견하실 수 있습니다. 해당 부분에 체크를 하시고 확인을 눌러주시면 됩니다.

이후 확인하고자 하는 프로그램을 실행하시고 Dialog를 여러 군데 왔다 갔다 해보시면 됩니다.

정상 프로그램 GDI 개체수

Dialog를 여러군데 왔다갔다 하는 과정에서 GDI항목의 값이 가파르게 오르고 수치가 떨어지지 않는 경우 메모리 누수가 발생한다고 볼 수 있습니다. 해당 동작을 여러 번 반복하면 메모리가 계속 증가하는 것도 확인하실 수 있습니다. 다만 GDI개체를 보는 게 그래픽의 메모리 할당량을 직접적으로 확인할 수 있어서 더 쉽습니다.

 

메모리 누수 프로그램 GDI 개체수

메모리 누수가 발생 중인 프로그램의 경우 GDI개체수가 증가하고 수치가 떨어지지 않습니다. 시간이 지나거나 해당 Dialog가 종료되면 메모리가 해제되면서 GDI 개체수가 본래 수치로 떨어져야 정상이지만 계속 유지하고 있다면 의심해야 합니다.

[GDI의 정의]

이쯤 되면 GDI의 정의를 확인하고 넘어가는 게 좋을 것 같습니다.

GDI는 Graphic Device Interface의 약자입니다. 기본적으로 그래픽 출력장치 API 한계치는 1만 개로 설정되어 있으며, 해당 수치를 넘어가는 경우 윈도의 화면이 하얗게 변하거나 시스템에 치명적인 이상을 발생시킬 수 있습니다. 그러나 임시적으로 레지스트리에서 GDI허용 임계치를 65535개까지 늘리는 방법도 존재하지만 해당 부분은 권장되지 않습니다.

장시간 프로그램이 가동되면 해당 임계치를 넘을 수밖에 없고 재부팅만이 답이 되기 때문입니다.

 

 

[메모리 누수 원인 확인]

그럼 이제 프로그램에서 메모리누수가 발견되었다면 어느 부분이 원인인지 파악해야 합니다. 대부분 원인은 아래와 같습니다. C++은 가비지컬렉션이 별도로 실행되지 않아 항상 메모리를 사용하면 반드시 해제를 해야 합니다.

 

GDI의 개체의 특성상 그래픽에 관련된 항목일 확률이 높기 때문에 OnCtlColor와 OnPaint 함수를 유심히 보시면 해결점을 금방 찾으실 수 있습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
HBRUSH FormTestCDialog::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
    HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
    HBRUSH m_brush = CreateSolidBrush(COLOR_3DHIGHLIGHT);
 
    if (nCtlColor == CTLCOLOR_STATIC)
    {
        //pDC->SetTextColor(RGB(0,0,0));  // 글자 색
        pDC->SetBkMode(TRANSPARENT);      // 배경투명
        //pDC->SetBkColor(RGB(255,255,255));  // 배경 색    
 
        return (HBRUSH)::GetStockObject(NULL_BRUSH);
    }
 
    return hbr;
}
 
cs

 

위 코드를 유심히 보시면 단순 STATIC형식을 가지는 Control을 배경 투명색으로 변경하는 함수입니다. 이 과정에서 HBRUSH를 사용하게 되는데요. 원인은 CreateSolidBrush에 있습니다. 해당 함수는 별도의 메모리를 사용해서 brush를 구성하고 포인트를 넘겨주는데요. 사용이 끝나고 Delete를 해주지 않아 누수가 발생됩니다.

 

해결 방법은 단순합니다. OnCtlColor 함수에서 Brush를 구성하는 게 아닌 별도 지역변수에서 Brush를 구성하고, 사용 이후 Window나 Class form을 Destructor를 호출할 때 메모리를 해제하면 됩니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
HBRUSH FormTestCDialog::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
    HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
    HBRUSH m_brush = CreateSolidBrush(COLOR_3DHIGHLIGHT);
 
    if (nCtlColor == CTLCOLOR_STATIC)
    {
        //pDC->SetTextColor(RGB(0,0,0));  // 글자 색
        pDC->SetBkMode(TRANSPARENT);      // 배경투명
        //pDC->SetBkColor(RGB(255,255,255));  // 배경 색    
 
        DeleteObject(m_brush);
        return (HBRUSH)::GetStockObject(NULL_BRUSH);
    }
 
    DeleteObject(m_brush);
    return hbr;
}
 
cs

위와 같이 반드시 사용된 메모리에 대해서 해제를 선언해야 합니다.

 

대부분의 메모리 누수는 해당 부분처럼 구조적인 문제일 가능성이 매우 높기 때문에 관련 소스 부분을 유심히 보기만 해도 충분히 원인을 찾아 해결하실 수 있습니다.