다음을 통해 공유


CRT 디버그 힙 정보

CRT 디버그 힙 및 관련 함수는 코드에서 메모리 관리 문제를 추적하고 디버그하는 여러 가지 방법을 제공합니다. 이를 사용하여 버퍼 오버런을 찾고 메모리 할당 및 메모리 상태를 추적하고 보고할 수 있습니다. 또한 고유한 앱 요구 사항에 맞게 고유한 디버그 할당 함수를 만들 수 있습니다.

디버그 힙을 사용하여 버퍼 오버런 찾기

프로그래머가 겪는 가장 일반적이고 난해한 두 가지 문제는 할당된 버퍼와 메모리 누수의 끝을 덮어쓰는 것입니다(더 이상 필요하지 않은 후 할당을 해제하지 못함). 디버그 힙을 사용하여 이러한 메모리 할당 문제를 효과적으로 해결할 수 있습니다.

힙 함수의 디버그 버전은 릴리스 빌드에서 사용하는 표준 또는 기본 버전을 호출합니다. 메모리 블록을 요청하면 디버그 힙 관리자는 기본 힙에서 요청한 것보다 약간 더 큰 메모리 블록을 할당하고 해당 블록의 부분에 대한 포인터를 반환합니다. 예를 들어 애플리케이션에 malloc( 10 ) 호출이 있을 경우, 릴리스 빌드 malloc 에서 10바이트 할당을 요청하는 기본 힙 할당 루틴을 호출합니다. 그러나 malloc 디버그 빌드에서는 호출합니다. 그러면 10바이트 및 약 36바이트의 추가 메모리 할당을 요청하는 기본 힙 할당 루틴을 호출 _malloc_dbg합니다. 디버그 힙의 모든 결과 메모리 블록은 할당된 순서에 따라 하나의 연결 리스트로 연결됩니다.

디버그 힙 루틴에 의해 할당된 추가 메모리는 부기 정보에 사용됩니다. 디버그 메모리 블록을 함께 연결하는 포인터와 할당된 지역의 덮어쓰기를 catch하기 위해 데이터의 양쪽에 작은 버퍼가 있습니다.

현재 디버그 힙의 부기 정보를 저장하는 데 사용되는 블록 헤더 구조는 헤더에 <crtdbg.h> 선언되고 CRT 원본 파일에 정의됩니다 <debug_heap.cpp> . 개념적으로 이 구조와 유사합니다.

typedef struct _CrtMemBlockHeader
{
// Pointer to the block allocated just before this one:
    _CrtMemBlockHeader* _block_header_next;
// Pointer to the block allocated just after this one:
    _CrtMemBlockHeader* _block_header_prev;
    char const*         _file_name;
    int                 _line_number;

    int                 _block_use;      // Type of block
    size_t              _data_size;      // Size of user block

    long                _request_number; // Allocation number
// Buffer just before (lower than) the user's memory:
    unsigned char       _gap[no_mans_land_size];

    // Followed by:
    // unsigned char    _data[_data_size];
    // unsigned char    _another_gap[no_mans_land_size];
} _CrtMemBlockHeader;

블록의 사용자 데이터 영역 양쪽에 있는 버퍼의 크기는 no_mans_land 현재 4바이트이며, 사용자의 메모리 블록 제한을 덮어쓰지 않았는지 확인하기 위해 디버그 힙 루틴에서 사용하는 알려진 바이트 값으로 채워집니다. 디버그 힙은 또한 새로운 메모리 블록을 알려진 값으로 채웁니다. 힙의 연결된 목록에 해제된 블록을 유지하도록 선택하면 이러한 해제된 블록도 알려진 값으로 채워집니다. 현재 실제 사용된 바이트 값은 다음과 같습니다.

no_mans_land (0xFD)
애플리케이션에서 사용하는 메모리 양쪽의 "no_mans_land" 버퍼는 현재 0xFD 채워집니다.

빈 블록(0xDD)
_CRTDBG_DELAY_FREE_MEM_DF 플래그를 설정할 경우 디버그 힙의 연결 리스트에서 사용하지 않는 빈 블록은 현재 0xDD로 채워져 있습니다.

새로운 개체(0xCD)
새 개체는 할당될 때 0xCD 채워집니다.

디버그 힙의 블록 형식

디버그 힙의 모든 메모리 블록은 다섯 가지 할당 형식 중 하나에 지정됩니다. 누수 탐지와 상태 보고 등의 목적에 따라 이러한 형식을 다르게 추적하고 보고합니다. 같은 디버그 힙 할당 함수 중 하나에 대한 직접 호출을 사용하여 블록의 형식을 할당하여 지정할 수 있습니다 _malloc_dbg. 디버그 힙에 있는 5가지 유형의 메모리 블록(구조체의 _CrtMemBlockHeader 멤버에 nBlockUse 설정)은 다음과 같습니다.

_NORMAL_BLOCK
Normal 블록을 호출 malloc 하거나 calloc 만듭니다. 일반 블록만 사용하려는 경우 클라이언트 블록이 필요하지 않은 경우 정의 _CRTDBG_MAP_ALLOC할 수 있습니다. _CRTDBG_MAP_ALLOC 는 모든 힙 할당 호출이 디버그 빌드의 해당 디버그에 매핑되도록 합니다. 해당 블록 헤더의 각 할당 호출에 대한 파일 이름 및 줄 번호 정보를 스토리지할 수 있습니다.

_CRT_BLOCK
여러 런타임 라이브러리 함수에 의해 내부적으로 할당된 메모리 블록은 CRT 블록으로 표시되어 별도로 처리될 수 있습니다. 결과적으로 누수 감지 및 기타 작업은 영향을 받지 않을 수 있습니다. 할당은 CRT 형식의 어떠한 블록도 할당하거나 할당 취소하거나 해제할 수 없습니다.

_CLIENT_BLOCK
애플리케이션은 지정한 할당 그룹을 이 형식의 메모리 블록으로 할당함으로써 디버깅을 위해 이 할당 그룹을 특별히 추적하여 디버그 힙 함수를 명시적으로 호출할 수 있습니다. 예를 들어 MFC는 모든 CObject 개체를 클라이언트 블록으로 할당합니다. 다른 애플리케이션은 클라이언트 블록에 다른 메모리 개체를 유지할 수 있습니다. 또한 보다 정교하게 추적하도록 클라이언트 블록의 하위 형식을 지정할 수 있습니다. 클라이언트 블록의 하위 형식을 지정하려면 16비트로 남긴 숫자를 변환하고 OR으로 _CLIENT_BLOCK연산을 실행하십시오. 예시:

#define MYSUBTYPE 4
freedbg(pbData, _CLIENT_BLOCK|(MYSUBTYPE<<16));

클라이언트 블록에 저장된 개체를 덤프하기 위한 클라이언트 제공 후크 함수를 사용하여 _CrtSetDumpClient설치할 수 있으며, 그러면 클라이언트 블록이 디버그 함수에 의해 덤프될 때마다 호출됩니다. _CrtDoForAllClientObjects 또한 디버그 힙의 모든 클라이언트 블록에 대해 애플리케이션에서 제공하는 지정된 함수를 호출하는 데 사용할 수 있습니다.

_FREE_BLOCK
대개 빈 블록은 리스트에서 제거됩니다. 해제된 메모리가 기록되지 않는지 확인하거나 낮은 메모리 조건을 시뮬레이트하려면 연결된 목록에 해제된 블록을 유지하여 Free로 표시하고 알려진 바이트 값(현재 0xDD)으로 채울 수 있습니다.

_IGNORE_BLOCK
일부 간격 동안 디버그 힙 작업을 해제할 수 있습니다. 그 동안 메모리 블록은 리스트에 보관되지만 무시 블록으로 표시됩니다.

지정된 블록의 형식과 하위 형식을 확인하려면 함수 _CrtReportBlockType 와 매크로를 _BLOCK_TYPE _BLOCK_SUBTYPE사용하고 . 매크로는 다음과 같이 정의 <crtdbg.h> 됩니다.

#define _BLOCK_TYPE(block)          (block & 0xFFFF)
#define _BLOCK_SUBTYPE(block)       (block >> 16 & 0xFFFF)

힙 무결성 및 메모리 누수 확인

디버그 힙의 기능 중 상당수는 코드 안에서 액세스해야 합니다. 다음 단원에서는 이러한 기능과 사용 방법에 대해 설명합니다.

_CrtCheckMemory
예를 들어 호출을 _CrtCheckMemory사용하여 언제든지 힙의 무결성을 확인할 수 있습니다. 이 함수는 힙의 모든 메모리 블록을 검사합니다. 메모리 블록 헤더 정보가 유효한지 확인하고 버퍼가 수정되지 않았는지 확인합니다.

_CrtSetDbgFlag
함수를 사용하여 _CrtSetDbgFlag 읽고 설정할 수 있는 내부 플래그_crtDbgFlag를 사용하여 디버그 힙이 할당을 추적하는 방법을 제어할 수 있습니다. 이 플래그를 변경하면 프로그램이 종료된 다음 탐지한 누수를 모두 보고할 때, 디버그 힙이 메모리 누수를 확인하도록 지시할 수 있습니다. 마찬가지로, 메모리 부족 상황을 시뮬레이션하기 위해 연결된 목록에 해제된 메모리 블록을 남겨 두도록 힙에 지시할 수 있습니다. 힙을 검사하면 이러한 해제된 블록이 완전히 검사되어 방해를 받지 않았는지 확인합니다.

플래그에는 _crtDbgFlag 다음 비트 필드가 포함됩니다.

비트 필드 기본값 설명
_CRTDBG_ALLOC_MEM_DF 설정 디버그 할당을 사용합니다. 이 비트가 꺼지면 할당은 함께 연결된 상태로 유지되지만 해당 블록 형식은 다음과 같습니다 _IGNORE_BLOCK.
_CRTDBG_DELAY_FREE_MEM_DF 끄기 부족한 메모리 조건을 시뮬레이션하는 것과 관련해서 메모리가 실제로 해제되는 것을 방지합니다. 이 비트가 켜지면 해제된 블록은 디버그 힙의 연결된 목록에 유지되지만 특수 바이트 값으로 _FREE_BLOCK 표시되고 채워집니다.
_CRTDBG_CHECK_ALWAYS_DF 끄기 할당 및 할당 취소 시 호출되는 원인 _CrtCheckMemory 입니다. 실행 속도가 느리지만 오류를 빠르게 catch합니다.
_CRTDBG_CHECK_CRT_DF 끄기 유형 _CRT_BLOCK 으로 표시된 블록이 누수 감지 및 상태 차이 작업에 포함되도록 합니다. 비트를 해제하면 위와 같은 작업을 하는 동안 런타임 라이브러리가 내부적으로 사용하는 메모리를 무시합니다.
_CRTDBG_LEAK_CHECK_DF 끄기 에 대한 호출을 통해 프로그램 종료 시 누수 검사가 수행되도록 합니다 _CrtDumpMemoryLeaks. 애플리케이션이 할당한 모든 메모리를 해제하는 데 실패하면 오류 보고서가 생성됩니다.

디버그 힙 구성

malloc, free, calloc, realloc, newdelete 등의 힙 함수에 대한 모든 호출은 디버그 힙에서 작동하는 해당 함수의 디버그 버전으로 확인됩니다. 메모리 블록을 해제할 경우, 디버그 힙은 자동으로 할당 영역 양쪽에 있는 버퍼의 무결성을 확인하며 덮어쓰기가 발생하면 오류 보고서를 만듭니다.

디버그 힙을 사용하려면

  • 애플리케이션의 디버그 빌드를 디버그 버전의 C 런타임 라이브러리와 연결합니다.

하나 이상의 _crtDbgFlag 비트 필드를 변경하고 플래그에 대한 새 상태를 만들려면

  1. 현재 _CrtSetDbgFlag 상태를 가져오기 위해 newFlag 매개 변수를 _CRTDBG_REPORT_FLAG로 설정한 상태로 _crtDbgFlag를 호출하고 반환된 값을 임시 변수에 저장합니다.

  2. 해당 비트 마스크가 있는 임시 변수에서 비트 | 연산자("or")를 사용하여 모든 비트를 켭니다(매니페스트 상수로 애플리케이션 코드에 표시됨).

  3. 적절한 비트 마스크의 비트 & 연산자("not" 또는 보수)가 있는 변수에서 비트 ~ 연산자("and")를 사용하여 다른 비트를 끕니다.

  4. _CrtSetDbgFlag에 대해 새로운 상태를 만들기 위해 임시 변수로 저장한 값에 설정한 newFlag 매개 변수를 사용하여 _crtDbgFlag를 호출합니다.

    예를 들어 다음 코드 줄은 자동 누수 검색을 사용하도록 설정하고 형식 _CRT_BLOCK블록에 대한 검사를 사용하지 않도록 설정합니다.

    // Get current flag
    int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );
    
    // Turn on leak-checking bit.
    tmpFlag |= _CRTDBG_LEAK_CHECK_DF;
    
    // Turn off CRT block checking bit.
    tmpFlag &= ~_CRTDBG_CHECK_CRT_DF;
    
    // Set flag to the new value.
    _CrtSetDbgFlag( tmpFlag );
    

new, delete_CLIENT_BLOCK C++ 디버그 힙의 할당

C 런타임 라이브러리의 디버그 버전은 C++ newdelete 연산자의 디버그 버전을 포함합니다. _CLIENT_BLOCK 할당 형식을 사용하는 경우 다음 예제와 같이 new 연산자의 디버그 버전을 직접 호출하거나 디버그 모드에서 new 연산자를 대체하는 매크로를 만들어야 합니다.

/* MyDbgNew.h
 Defines global operator new to allocate from
 client blocks
*/

#ifdef _DEBUG
   #define DEBUG_CLIENTBLOCK   new( _CLIENT_BLOCK, __FILE__, __LINE__)
#else
   #define DEBUG_CLIENTBLOCK
#endif // _DEBUG

/* MyApp.cpp
        Use a default workspace for a Console Application to
 *      build a Debug version of this code
*/

#include "crtdbg.h"
#include "mydbgnew.h"

#ifdef _DEBUG
#define new DEBUG_CLIENTBLOCK
#endif

int main( )   {
    char *p1;
    p1 =  new char[40];
    _CrtMemDumpAllObjectsSince( NULL );
}

delete 연산자의 디버그 버전은 모든 블록 형식에 대해 작동하며, 릴리스 버전을 컴파일할 때 프로그램을 변경할 필요가 없습니다.

힙 상태 보고 함수

지정된 시간에 힙 상태의 요약 스냅샷을 캡처하려면 다음에 정의된 <crtdbg.h>구조를 사용합니다_CrtMemState.

typedef struct _CrtMemState
{
    // Pointer to the most recently allocated block:
    struct _CrtMemBlockHeader * pBlockHeader;
    // A counter for each of the 5 types of block:
    size_t lCounts[_MAX_BLOCKS];
    // Total bytes allocated in each block type:
    size_t lSizes[_MAX_BLOCKS];
    // The most bytes allocated at a time up to now:
    size_t lHighWaterCount;
    // The total bytes allocated at present:
    size_t lTotalCount;
} _CrtMemState;

이 구조체는 가장 최근에 할당된 첫 번째 블록에 대한 포인터를 디버그 힙의 연결 리스트에 저장합니다. 그런 다음, 두 배열에서 각 유형의 메모리 블록(_NORMAL_BLOCK, _CLIENT_BLOCK, _FREE_BLOCK등)이 목록에 있는 수와 각 블록 형식에 할당된 바이트 수를 기록합니다. 마지막으로 해당 포인트까지 전체적으로 힙에서 할당된 가장 큰 바이트 수와 현재 할당된 바이트 수를 기록합니다.

기타 CRT 보고 함수

다음 함수는 힙의 상태와 내용을 보고하며 그 정보를 사용하여 메모리 누수 등의 문제를 탐지합니다.

함수 설명
_CrtMemCheckpoint 애플리케이션에서 제공하는 구조에 _CrtMemState 힙의 스냅샷을 저장합니다.
_CrtMemDifference 두 메모리 상태 구조체를 비교하고 세 번째 상태 구조체에 그 차이를 저장하며, 두 상태가 다른 경우 TRUE를 반환합니다.
_CrtMemDumpStatistics 지정된 _CrtMemState 구조를 덤프합니다. 구조체에는 지정한 순간의 디버그 힙 상태 스냅샷이나 두 스냅샷의 차이점이 포함될 수 있습니다.
_CrtMemDumpAllObjectsSince 지정한 스냅샷이 힙의 스냅샷이거나 실행을 시작할 때 만들어진 스냅샷이기 때문에 할당된 모든 개체 정보를 덤프합니다. 블록을 덤프 _CLIENT_BLOCK 할 때마다 애플리케이션에서 제공하는 후크 함수를 호출합니다(을 사용하여 _CrtSetDumpClient설치된 경우).
_CrtDumpMemoryLeaks 프로그램 실행을 시작한 이후로 메모리 누수가 발생했는지 확인하고, 메모리 누수를 탐지한 경우 할당된 개체를 모두 덤프합니다. 블록을 덤프 _CLIENT_BLOCK 할 때마다 _CrtDumpMemoryLeaks 애플리케이션에서 제공하는 후크 함수를 호출합니다(을 사용하여 _CrtSetDumpClient설치된 경우).

힙 할당 요청 추적

어설션 또는 보고 매크로의 원본 파일 이름과 줄 번호를 아는 것은 문제의 원인을 찾는 데 유용한 경우가 많습니다. 힙 할당 함수도 마찬가지입니다. 애플리케이션의 논리 트리에서 여러 적절한 지점에 매크로를 삽입할 수 있지만 할당은 종종 여러 다른 위치에서 호출되는 함수에 저장됩니다. 문제는 잘못된 할당을 만든 코드 줄이 아닙니다. 대신, 해당 코드 줄에 의해 만들어진 수천 개의 할당 중 하나가 나쁜 이유입니다.

고유 할당 요청 번호 및 _crtBreakAlloc

잘못된 특정 힙 할당 호출을 식별하는 간단한 방법이 있습니다. 디버그 힙의 각 블록과 연결된 고유 할당 요청 번호를 활용합니다. 덤프 함수 중 하나가 블록에 대한 정보를 보고하면 이 할당 요청 번호는 중괄호로 묶어 표시합니다(예: "{36}").

잘못 할당된 블록의 할당 요청 번호를 알게 되면 이 숫자를 전달하여 _CrtSetBreakAlloc 중단점을 만들 수 있습니다. 블록을 할당하기 직전에 실행이 중단되면 역추적하여 잘못된 호출에 관련된 루틴이 어느 것인지 확인할 수 있습니다. 다시 컴파일하지 않도록 하려면 관심 있는 할당 요청 번호로 설정 _crtBreakAlloc 하여 디버거에서 동일한 작업을 수행할 수 있습니다.

할당 루틴의 디버그 버전 만들기

더 복잡한 방법은 힙 할당 함수 버전과 비슷한 _dbg 고유한 할당 루틴의 디버그 버전을 만드는 것입니다. 그런 다음 원본 파일 및 줄 번호 인수를 기본 힙 할당 루틴으로 전달할 수 있으며 잘못된 할당이 발생한 위치를 즉시 확인할 수 있습니다.

예를 들어 애플리케이션에 다음 예제와 유사하게 일반적으로 사용되는 루틴이 포함되어 있다고 가정합니다.

int addNewRecord(struct RecStruct * prevRecord,
                 int recType, int recAccess)
{
    // ...code omitted through actual allocation...
    if ((newRec = malloc(recSize)) == NULL)
    // ... rest of routine omitted too ...
}

헤더 파일에서 다음 예제와 같은 코드를 추가할 수 있습니다.

#ifdef _DEBUG
#define  addNewRecord(p, t, a) \
            addNewRecord(p, t, a, __FILE__, __LINE__)
#endif

그런 다음 다음과 같이 레코드 작성 루틴에서 할당을 변경할 수 있습니다.

int addNewRecord(struct RecStruct *prevRecord,
                int recType, int recAccess
#ifdef _DEBUG
               , const char *srcFile, int srcLine
#endif
    )
{
    /* ... code omitted through actual allocation ... */
    if ((newRec = _malloc_dbg(recSize, _NORMAL_BLOCK,
            srcFile, scrLine)) == NULL)
    /* ... rest of routine omitted too ... */
}

이제 addNewRecord를 호출한 소스 파일 이름과 줄 번호는 디버그 힙에 할당된 각 결과 블록에 저장되며 해당 블록을 검사할 때 보고됩니다.

참고 항목

네이티브 코드 디버그