다음을 통해 공유


MFC 디버깅 기술

MFC 프로그램을 디버깅하는 경우 이러한 디버깅 기술이 유용할 수 있습니다.

AfxDebugBreak

MFC는 소스 코드에서 중단점을 하드 코딩하기 위한 특수 AfxDebugBreak 함수를 제공합니다.

AfxDebugBreak( );

Intel 플랫폼에서 AfxDebugBreak는 커널 코드가 아닌 소스 코드에서 중단되는 다음 코드를 생성합니다.

_asm int 3

다른 플랫폼에서는 AfxDebugBreak가 단지 DebugBreak를 호출합니다.

릴리스 빌드를 만들 때는 AfxDebugBreak 문을 제거하거나, #ifdef _DEBUG을 사용해 해당 문을 둘러싸야 합니다.

TRACE 매크로

디버거 출력 창에 프로그램의 메시지를 표시하려면 ATLTRACE 매크로 또는 MFC TRACE 매크로를 사용할 수 있습니다. 어설션과 마찬가지로 추적 매크로는 프로그램의 디버그 버전에서만 활성화되며 릴리스 버전에서 컴파일될 때 사라집니다.

다음 예제에서는 TRACE 매크로를 사용할 수 있는 몇 가지 방법을 보여 줍니다. 마찬가지로 printfTRACE 매크로는 여러 인수를 처리할 수 있습니다.

int x = 1;
int y = 16;
float z = 32.0;
TRACE( "This is a TRACE statement\n" );

TRACE( "The value of x is %d\n", x );

TRACE( "x = %d and y = %d\n", x, y );

TRACE( "x = %d and y = %x and z = %f\n", x, y, z );

TRACE 매크로는 char* 및 wchar_t* 매개 변수를 모두 적절하게 처리합니다. 다음 예제에서는 다른 형식의 문자열 매개 변수와 함께 TRACE 매크로를 사용하는 방법을 보여 줍니다.

TRACE( "This is a test of the TRACE macro that uses an ANSI string: %s %d\n", "The number is:", 2);

TRACE( L"This is a test of the TRACE macro that uses a UNICODE string: %s %d\n", L"The number is:", 2);

TRACE( _T("This is a test of the TRACE macro that uses a TCHAR string: %s %d\n"), _T("The number is:"), 2);

TRACE 매크로에 대한 자세한 내용은 Diagnostic Services를 참조하세요.

MFC에서 메모리 누수 감지

MFC는 할당되었지만 할당 취소되지 않은 메모리를 검색하기 위한 클래스 및 함수를 제공합니다.

메모리 할당 추적

MFC에서는 연산자 대신 매크로 DEBUG_NEW 사용하여 메모리 누수 위치를 찾을 수 있습니다. 디버그 버전의 프로그램에서, DEBUG_NEW는 할당된 각 개체의 파일 이름과 줄 번호를 추적합니다. 프로그램의 릴리즈 버전을 컴파일할 때 DEBUG_NEW는 파일 이름과 줄 번호 정보 없이 간단한 new 연산으로 변환됩니다. 따라서 프로그램의 릴리스 버전에서 속도 페널티를 지불하지 않습니다.

DEBUG_NEW로 대체하기 위해 전체 프로그램을 다시 작성하고 싶지 않다면, 소스 파일에서 다음 매크로를 정의할 수 있습니다.

#define new DEBUG_NEW

개체 덤프를 수행할 때 할당된 DEBUG_NEW 각 개체에는 할당된 파일 및 줄 번호가 표시되므로 메모리 누수의 원인을 정확히 파악할 수 있습니다.

MFC 프레임워크의 디버그 버전은 DEBUG_NEW를 자동으로 사용하지만, 귀하의 코드는 그렇지 않습니다. DEBUG_NEW의 이점을 얻으려면 명시적으로 DEBUG_NEW을 사용하거나, 위에서 설명한대로 #define new을 사용해야 합니다.

메모리 진단 사용

메모리 진단 기능을 사용하려면 먼저 진단 추적을 사용하도록 설정해야 합니다.

메모리 진단을 사용하거나 사용하지 않도록 설정하려면

  • 전역 함수 AfxEnableMemoryTracking을 호출하여 진단 메모리 할당자를 사용하거나 사용하지 않도록 설정합니다. 메모리 진단은 디버그 라이브러리에서 기본적으로 설정되어 있으므로 일반적으로 이 함수를 사용하여 일시적으로 해제하여 프로그램 실행 속도를 높이고 진단 출력을 줄입니다.

    afxMemDF를 사용하여 특정 메모리 진단 기능을 선택하려면

  • 메모리 진단 기능을 보다 정확하게 제어하려면 MFC 전역 변수 afxMemDF의 값을 설정하여 개별 메모리 진단 기능을 선택적으로 켜고 끌 수 있습니다. 이 변수는 열거형 형식 afxMemDF에 지정된 대로 다음 값을 가질 수 있습니다.

    가치 설명
    allocMemDF 기본값인 진단 메모리 할당자를 켭니다.
    지연FreeMemDF 호출 delete 시 또는 free 프로그램이 종료될 때까지 메모리 해제를 지연합니다. 이로 인해 프로그램에서 가능한 최대 메모리 양을 할당하게 됩니다.
    checkAlwaysMemDF 메모리가 할당되거나 해제될 때마다 AfxCheckMemory 를 호출합니다.

    이러한 값은 다음과 같이 논리 OR 연산을 수행하여 함께 사용할 수 있습니다.

    afxMemDF = allocMemDF | delayFreeMemDF | checkAlwaysMemDF;
    

메모리 스냅샷 만들기

  1. CMemoryState 개체를 만들고 CMemoryState::Checkpoint 멤버 함수를 호출합니다. 그러면 첫 번째 메모리 스냅샷이 만들어집니다.

  2. 프로그램에서 메모리 할당 및 할당 취소 작업을 수행한 후 다른 CMemoryState 개체를 만들고 해당 개체를 호출 Checkpoint 합니다. 메모리 사용량의 두 번째 스냅샷을 가져옵니다.

  3. 세 번째 CMemoryState 개체를 만들고 해당 CMemoryState::D ifference 멤버 함수를 호출하여 이전 CMemoryState 의 두 개체를 인수로 제공합니다. 두 메모리 상태 간에 차이가 있는 경우 함수는 Difference 0이 아닌 값을 반환합니다. 이는 일부 메모리 블록의 할당이 취소되지 않았음을 나타냅니다.

    이 예제에서는 코드의 모양을 보여줍니다.

    // Declare the variables needed
    #ifdef _DEBUG
        CMemoryState oldMemState, newMemState, diffMemState;
        oldMemState.Checkpoint();
    #endif
    
        // Do your memory allocations and deallocations.
        CString s("This is a frame variable");
        // The next object is a heap object.
        CPerson* p = new CPerson( "Smith", "Alan", "581-0215" );
    
    #ifdef _DEBUG
        newMemState.Checkpoint();
        if( diffMemState.Difference( oldMemState, newMemState ) )
        {
            TRACE( "Memory leaked!\n" );
        }
    #endif
    

    메모리 검사 문은 프로그램의 디버그 버전에서만 컴파일되도록 #ifdef _DEBUG/#endif 블록으로 대괄호로 묶입니다.

    이제 메모리 누수에 대해 알게 되었으므로 다른 멤버 함수인 CMemoryState::D umpStatistics 를 사용하여 찾을 수 있습니다.

메모리 통계 보기

CMemoryState::Difference 함수는 두 개의 메모리 상태 개체를 비교하여 시작 상태와 끝 상태 사이에서 힙에서 할당 해제되지 않은 개체를 검출합니다. 메모리 스냅샷을 만들고 CMemoryState::Difference을 사용하여 비교한 후, CMemoryState::DumpStatistics를 호출하여 해제되지 않은 개체에 대한 정보를 가져올 수 있습니다.

다음 예제를 고려하세요.

if( diffMemState.Difference( oldMemState, newMemState ) )
{
    TRACE( "Memory leaked!\n" );
    diffMemState.DumpStatistics();
}

예제의 샘플 덤프는 다음과 같습니다.

0 bytes in 0 Free Blocks
22 bytes in 1 Object Blocks
45 bytes in 4 Non-Object Blocks
Largest number used: 67 bytes
Total allocations: 67 bytes

afxMemDFdelayFreeMemDF으로 설정된 경우 할당 해제가 지연되는 블록을 자유 블록이라고 합니다.

두 번째 줄에 표시된 일반 개체 블록은 힙에 할당된 상태로 유지됩니다.

개체가 아닌 블록에는 .을 사용하여 할당된 배열 및 구조체가 new포함됩니다. 이 경우 4개의 비객체 블록이 힙에 할당되었지만 해제되지 않았습니다.

Largest number used 는 언제든지 프로그램에서 사용하는 최대 메모리를 제공합니다.

Total allocations 는 프로그램에서 사용하는 총 메모리 양을 제공합니다.

개체 덤프를 가져옵니다.

MFC 프로그램에서 CMemoryState::DumpAllObjectsSince를 사용하여 할당 취소되지 않은 힙상의 모든 개체에 대한 설명을 덤프할 수 있습니다. DumpAllObjectsSince 는 마지막 CMemoryState::Checkpoint 이후 할당된 모든 개체를 덤프합니다. Checkpoint 호출이 발생하지 않은 경우, DumpAllObjectsSince 현재 메모리에 있는 모든 객체와 비객체를 덤프합니다.

비고

MFC 개체 덤프를 사용하려면 먼저 진단 추적을 사용하도록 설정해야 합니다.

비고

MFC는 프로그램이 종료될 때 유출된 모든 개체를 자동으로 덤프하므로 해당 시점에서 개체를 덤프하는 코드를 만들 필요가 없습니다.

다음 코드는 두 개의 메모리 상태를 비교하여 메모리 누수를 테스트하고 누수가 감지되면 모든 개체를 덤프합니다.

if( diffMemState.Difference( oldMemState, newMemState ) )
{
    TRACE( "Memory leaked!\n" );
    diffMemState.DumpAllObjectsSince();
}

덤프의 내용은 다음과 같습니다.

Dumping objects ->

{5} strcore.cpp(80) : non-object block at $00A7521A, 9 bytes long
{4} strcore.cpp(80) : non-object block at $00A751F8, 5 bytes long
{3} strcore.cpp(80) : non-object block at $00A751D6, 6 bytes long
{2} a CPerson at $51A4

Last Name: Smith
First Name: Alan
Phone #: 581-0215

{1} strcore.cpp(80) : non-object block at $00A7516E, 25 bytes long

대부분의 줄의 시작 부분에 있는 중괄호로 된 숫자는 개체가 할당된 순서를 지정합니다. 가장 최근에 할당된 개체의 개수가 가장 높고 덤프 맨 위에 나타납니다.

개체 덤프에서 최대한 많은 정보를 얻으려면, Dump에서 파생된 개체의 CObject 멤버 함수를 재정의하여 개체 덤프를 맞춤화할 수 있습니다.

전역 변수 _afxBreakAlloc 를 중괄호에 표시된 숫자로 설정하여 특정 메모리 할당에 중단점을 설정할 수 있습니다. 프로그램을 다시 실행하면 해당 할당이 발생할 때 디버거가 실행을 중단합니다. 그런 다음 호출 스택을 확인하여 프로그램이 해당 지점에 도달한 방법을 확인할 수 있습니다.

C 런타임 라이브러리에는 C 런타임 할당에 사용할 수 있는 유사한 함수 _CrtSetBreakAlloc 있습니다.

메모리 덤프 해석

이 개체 덤프를 자세히 확인해보세요.

{5} strcore.cpp(80) : non-object block at $00A7521A, 9 bytes long
{4} strcore.cpp(80) : non-object block at $00A751F8, 5 bytes long
{3} strcore.cpp(80) : non-object block at $00A751D6, 6 bytes long
{2} a CPerson at $51A4

Last Name: Smith
First Name: Alan
Phone #: 581-0215

{1} strcore.cpp(80) : non-object block at $00A7516E, 25 bytes long

이 덤프를 생성한 프로그램에는 스택에 하나와 힙에 하나씩 두 개의 명시적 할당만 있었습니다.

// Do your memory allocations and deallocations.
CString s("This is a frame variable");
// The next object is a heap object.
CPerson* p = new CPerson( "Smith", "Alan", "581-0215" );

CPerson 생성자는 char의 포인터인 세 개의 인수를 받아, 이 인수들은 CString 멤버 변수를 초기화하는 데 사용됩니다. 메모리 덤프에서 CPerson 개체와 세 개의 비객체 블록(3, 4 및 5)을 볼 수 있습니다. CString 멤버 변수의 문자를 포함하며, 객체 소멸자 CPerson가 호출될 때 삭제되지 않습니다.

블록 번호 2는 CPerson 개체 자체입니다. $51A4는 블록의 주소를 나타내며, CPerson에서 호출할 때 ::Dump 에 의해 출력된 개체의 내용이 잇습니다.

블록 번호 1은 프레임 변수의 문자 수와 CString 일치하는 시퀀스 번호 및 크기 때문에 프레임 CString 변수와 연결되어 있다고 추측할 수 있습니다. 프레임에 할당된 변수는 프레임이 범위를 벗어나면 자동으로 할당 취소됩니다.

프레임 변수

일반적으로 프레임 변수가 범위를 벗어날 때 자동으로 할당 취소되므로 프레임 변수와 연결된 힙 개체에 대해 걱정할 필요가 없습니다. 메모리 진단 덤프에서 혼란을 방지하려면 호출이 프레임 변수의 범위를 벗어나도록 배치 Checkpoint 해야 합니다. 예를 들어 다음과 같이 이전 할당 코드 주위에 범위 대괄호를 배치합니다.

oldMemState.Checkpoint();
{
    // Do your memory allocations and deallocations ...
    CString s("This is a frame variable");
    // The next object is a heap object.
    CPerson* p = new CPerson( "Smith", "Alan", "581-0215" );
}
newMemState.Checkpoint();

범위 대괄호가 있는 경우 이 예제의 메모리 덤프는 다음과 같습니다.

Dumping objects ->

{5} strcore.cpp(80) : non-object block at $00A7521A, 9 bytes long
{4} strcore.cpp(80) : non-object block at $00A751F8, 5 bytes long
{3} strcore.cpp(80) : non-object block at $00A751D6, 6 bytes long
{2} a CPerson at $51A4

Last Name: Smith
First Name: Alan
Phone #: 581-0215

비객체 할당

일부 할당은 개체(예: CPerson)이며 일부는 nonobject 할당입니다. "Nonobject 할당"은 CObject에서 파생되지 않은 객체 또는 기본 C 데이터 타입(예: char, int, 또는 long)의 할당입니다. CObject 파생 클래스가 내부 버퍼와 같은 추가 공간을 할당하는 경우 해당 개체는 개체 할당과 nonobject 할당을 모두 표시합니다.

메모리 누수 방지

위의 코드에서는 프레임 변수와 연결된 메모리 블록의 CString 할당이 자동으로 취소되고 메모리 누수로 표시되지 않습니다. 범위 지정 규칙에 따른 자동 메모리 해제는 스코프 변수와 관련된 대부분의 메모리 누수를 해결합니다.

그러나 힙에 할당된 개체의 경우 메모리 누수를 방지하기 위해 개체를 명시적으로 삭제해야 합니다. 이전 예제에서 마지막 메모리 누수 값을 정리하려면 다음과 같이 힙에 할당된 개체를 삭제 CPerson 합니다.

{
    // Do your memory allocations and deallocations.
    CString s("This is a frame variable");
    // The next object is a heap object.
    CPerson* p = new CPerson( "Smith", "Alan", "581-0215" );
    delete p;
}

개체 덤프의 사용자 지정

CObject에서 클래스를 파생하는 경우, Dump 멤버 함수를 재정의하여 DumpAllObjectsSince를 사용해 개체를 출력 창에 출력할 때 추가 정보를 제공할 수 있습니다.

이 함수는 Dump 개체의 멤버 변수에 대한 텍스트 표현을 덤프 컨텍스트(CDumpContext)에 씁니다. 덤프 컨텍스트는 I/O 스트림과 유사합니다. 추가 연산자(<<)를 사용하여 데이터를 CDumpContext에 보낼 수 있습니다.

Dump 함수를 오버라이드할 때, 먼저 기본 클래스 버전의 Dump을(를) 호출하여 기본 클래스 객체의 내용을 덤프해야 합니다. 그런 다음 파생 클래스의 각 멤버 변수에 대한 텍스트 설명 및 값을 출력합니다.

함수 선언은 Dump 다음과 같습니다.

class CPerson : public CObject
{
public:
#ifdef _DEBUG
    virtual void Dump( CDumpContext& dc ) const;
#endif

    CString m_firstName;
    CString m_lastName;
    // And so on...
};

개체 덤프는 프로그램을 디버깅할 때만 의미가 있으므로 함수 선언 Dump#ifdef _DEBUG/#endif 블록으로 대괄호로 묶입니다.

다음 예제에서 함수는 Dump 먼저 해당 기본 클래스에 Dump 대한 함수를 호출합니다. 그런 다음, 진단 스트림에 멤버의 값과 함께 각 멤버 변수에 대한 간단한 설명을 씁니다.

#ifdef _DEBUG
void CPerson::Dump( CDumpContext& dc ) const
{
    // Call the base class function first.
    CObject::Dump( dc );

    // Now do the stuff for our specific class.
    dc << "last name: " << m_lastName << "\n"
        << "first name: " << m_firstName << "\n";
}
#endif

덤프 출력의 CDumpContext 위치를 지정하려면 인수를 제공해야 합니다. MFC의 디버그 버전은 디버거에 출력을 보내는 미리 CDumpContext 정의된 afxDump 개체를 제공합니다.

CPerson* pMyPerson = new CPerson;
// Set some fields of the CPerson object.
//...
// Now dump the contents.
#ifdef _DEBUG
pMyPerson->Dump( afxDump );
#endif

MFC 디버그 빌드의 크기 줄이기

대규모 MFC 애플리케이션에 대한 디버그 정보는 디스크 공간을 많이 차지할 수 있습니다. 다음 절차 중 하나를 사용하여 크기를 줄일 수 있습니다.

  1. /Z7 대신 /Z7, /Zi, /ZI(디버그 정보 형식) 옵션을 사용하여 MFC 라이브러리를 다시 빌드합니다. 이러한 옵션은 전체 라이브러리에 대한 디버그 정보가 포함된 단일 PDB(프로그램 데이터베이스) 파일을 빌드하여 중복성을 줄이고 공간을 절약합니다.

  2. 디버그 정보 없이 MFC 라이브러리를 다시 빌드합니다( /Z7, /Zi, /ZI(디버그 정보 형식) 옵션 없음). 이 경우 디버그 정보가 부족하여 MFC 라이브러리 코드 내에서 대부분의 디버거 기능을 사용할 수 없지만 MFC 라이브러리가 이미 철저히 디버그되어 있으므로 문제가 되지 않을 수 있습니다.

  3. 아래에 설명된 대로 선택한 모듈에 대한 디버그 정보를 사용하여 고유한 애플리케이션을 빌드합니다.

선택한 모듈에 대한 디버그 정보를 사용하여 MFC 앱 빌드

MFC 디버그 라이브러리를 사용하여 선택한 모듈을 빌드하면 해당 모듈의 단계별 실행 및 기타 디버그 기능을 사용할 수 있습니다. 이 절차에서는 프로젝트의 디버그 및 릴리스 구성을 모두 사용하므로 다음 단계에 설명된 변경이 필요하고 전체 릴리스 빌드가 필요한 경우 "모두 다시 빌드"를 수행해야 합니다.

  1. 솔루션 탐색기에서 프로젝트를 선택합니다.

  2. 보기 메뉴에서 속성 페이지를 선택합니다.

  3. 먼저 새 프로젝트 구성을 만듭니다.

    1. <프로젝트> 속성 페이지 대화 상자에서 Configuration Manager 단추를 클릭합니다.

    2. Configuration Manager 대화 상자에서 그리드에서 프로젝트를 찾습니다. 구성 열에서 새로 만들기...<를 선택합니다>.

    3. 새 프로젝트 구성 대화 상자의 프로젝트 구성 이름 상자에 새 구성의 이름(예: "부분 디버그")을 입력합니다.

    4. 목록의 복사 설정에서릴리스를 선택합니다.

    5. [확인]을 클릭하여 [새 프로젝트 구성] 대화 상자를 닫습니다.

    6. Configuration Manager 대화 상자를 닫습니다.

  4. 이제 전체 프로젝트에 대한 옵션을 설정합니다.

    1. 속성 페이지 대화 상자의 구성 속성 폴더에서 일반 범주를 선택합니다.

    2. 프로젝트 설정 표에서 프로젝트 기본값 을 확장합니다(필요한 경우).

    3. 프로젝트 기본값에서 MFC 사용을 찾습니다. 현재 설정은 표의 오른쪽 열에 나타납니다. 현재 설정을 클릭하고 정적 라이브러리에서 MFC를 사용하도록 변경합니다.

    4. 속성 페이지 대화 상자의 왼쪽 창에서 C/C++ 폴더를 열고 전처리기를 선택합니다. 속성 표에서 전처리기 정의를 찾아 "NDEBUG"를 "_DEBUG"로 바꿉니다.

    5. 속성 페이지 대화 상자의 왼쪽 창에서 링커 폴더를 열고 입력 범주를 선택합니다. 속성 표에서 추가 종속성을 찾습니다. 추가 종속성 설정에서 "NAFXCWD"를 입력합니다. LIB" 및 "LIBCMT."

    6. [확인]을 클릭하여 새 빌드 옵션을 저장하고 [속성 페이지] 대화 상자를 닫습니다.

  5. 빌드 메뉴에서 다시빌드를 선택합니다. 이렇게 하면 모듈에서 모든 디버그 정보가 제거되지만 MFC 라이브러리에는 영향을 주지 않습니다.

  6. 이제 애플리케이션에서 선택한 모듈에 디버그 정보를 다시 추가해야 합니다. 중단점을 설정하고 디버그 정보를 사용하여 컴파일한 모듈에서만 다른 디버거 함수를 수행할 수 있습니다. 디버그 정보를 포함하려는 각 프로젝트 파일에 대해 다음 단계를 수행합니다.

    1. 솔루션 탐색기에서 프로젝트 아래에 있는 원본 파일 폴더를 엽니다.

    2. 디버그 정보를 설정할 파일을 선택합니다.

    3. 보기 메뉴에서 속성 페이지를 선택합니다.

    4. 속성 페이지 대화 상자의 구성 설정 폴더에서 C/C++ 폴더를 연 다음 일반 범주를 선택합니다.

    5. 속성 표에서 디버그 정보 형식을 찾습니다.

    6. 디버그 정보 형식 설정을 클릭하고 디버그 정보에 대해 원하는 옵션(일반적으로 /ZI)을 선택합니다.

    7. 애플리케이션 마법사에서 생성된 애플리케이션을 사용하거나 미리 컴파일된 헤더가 있는 경우 다른 모듈을 컴파일하기 전에 미리 컴파일된 헤더를 해제하거나 다시 컴파일해야 합니다. 그렇지 않으면 경고 C4650 및 오류 메시지 C2855가 표시됩니다. > 변경하여 미리 컴파일된 헤더를 끌 수 있습니다(구성 속성 폴더, C/C++ 하위 폴더, 미리 컴파일된 헤더 범주).

  7. 빌드 메뉴에서 빌드 를 선택하여 오래된 프로젝트 파일을 다시 빌드 합니다.

    이 항목에 설명된 기술 대신 외부 메이크파일을 사용하여 각 파일에 대한 개별 옵션을 정의할 수 있습니다. 이 경우 MFC 디버그 라이브러리와 연결하려면 각 모듈에 대한 _DEBUG 플래그를 정의해야 합니다. MFC 릴리스 라이브러리를 사용하려면 NDEBUG를 정의해야 합니다. 외부 메이크파일 작성에 대한 자세한 내용은 NMAKE 참조를 참조하세요.