Compartilhar via


Técnicas de depuração MFC

Se você estiver depurando um programa MFC, essas técnicas de depuração podem ser úteis.

AfxDebugBreak

O MFC fornece uma função AfxDebugBreak especial para pontos de interrupção de codificação rígida no código-fonte:

AfxDebugBreak( );

Em plataformas Intel, AfxDebugBreak produz o seguinte código, que interrompe o código-fonte em vez do código do kernel:

_asm int 3

Em outras plataformas, AfxDebugBreak simplesmente chama DebugBreak.

Não se esqueça de remover as instruções AfxDebugBreak ao criar uma versão de lançamento ou use #ifdef _DEBUG para envolvê-las.

A macro TRACE

Para exibir mensagens do programa na janela Saída do depurador, você pode usar a macro ATLTRACE ou a macro MFC TRACE . Assim como as asserções, as macros de rastreamento estão ativas apenas na versão de depuração do seu programa e desaparecem quando compiladas na versão de lançamento.

Os exemplos a seguir mostram algumas das maneiras de usar a macro TRACE . Por exemplo printf, a macro TRACE pode lidar com vários argumentos.

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 );

A macro TRACE manipula adequadamente os parâmetros char* e wchar_t*. Os exemplos a seguir demonstram o uso da macro TRACE junto com diferentes tipos de parâmetros de cadeia de caracteres.

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);

Para obter mais informações sobre a macro TRACE , consulte Serviços de Diagnóstico.

Detectando vazamentos de memória no MFC

O MFC fornece classes e funções para detectar memória alocada, mas nunca desalocada.

Acompanhamento de alocações de memória

No MFC, você pode usar a macro DEBUG_NEW no lugar do novo operador para ajudar a localizar vazamentos de memória. Na versão de depuração do seu programa, DEBUG_NEW mantém o controle do nome do arquivo e do número de linha para cada objeto que aloca. Quando você compila uma versão de Release do seu programa, DEBUG_NEW se resolve em uma operação nova simples sem as informações de nome de arquivo e número de linha. Dessa forma, você não paga nenhuma penalidade de velocidade na versão de lançamento do seu programa.

Se você não quiser reescrever todo o programa para usar DEBUG_NEW no lugar do novo, poderá definir essa macro em seus arquivos de origem:

#define new DEBUG_NEW

Ao fazer um despejo de objeto, cada objeto alocado com DEBUG_NEW mostrará o arquivo e o número de linha onde foi alocado, permitindo identificar as fontes de vazamentos de memória.

A versão de depuração da estrutura MFC usa DEBUG_NEW automaticamente, mas seu código não. Se você quiser os benefícios de DEBUG_NEW, deverá usar DEBUG_NEW explicitamente ou #define novo conforme mostrado acima.

Habilitando o diagnóstico de memória

Antes de poder usar os recursos de diagnóstico de memória, você deve habilitar o rastreamento de diagnóstico.

Para habilitar ou desabilitar o diagnóstico de memória

  • Chame a função global AfxEnableMemoryTracking para habilitar ou desabilitar o alocador de memória de diagnóstico. Como o diagnóstico de memória está ativado por padrão na biblioteca de depuração, você normalmente usará essa função para desativá-los temporariamente, o que aumenta a velocidade de execução do programa e reduz a saída de diagnóstico.

    Para selecionar recursos específicos de diagnóstico de memória com afxMemDF

  • Se você quiser um controle mais preciso sobre os recursos de diagnóstico de memória, poderá ativar e desativar seletivamente os recursos de diagnóstico de memória individuais definindo o valor da variável global do MFC afxMemDF. Essa variável pode ter os seguintes valores, conforme especificado pelo tipo enumerado afxMemDF.

    Valor Descrição
    allocMemDF Ative o alocador de memória de diagnóstico (padrão).
    atrasoFreeMemDF Atrasar a liberação de memória ao chamar delete ou free até que o programa seja encerrado. Isso fará com que seu programa aloque a quantidade máxima possível de memória.
    checkAlwaysMemDF Chame AfxCheckMemory toda vez que a memória for alocada ou liberada.

    Esses valores podem ser usados em combinação executando uma operação lógico-OR, conforme mostrado aqui:

    afxMemDF = allocMemDF | delayFreeMemDF | checkAlwaysMemDF;
    

Tirando instantâneos de memória

  1. Crie um objeto CMemoryState e chame a função membro CMemoryState::Checkpoint . Isso cria o primeiro instantâneo de memória.

  2. Depois que o programa executar suas operações de alocação e desalocação de memória, crie outro objeto CMemoryState e chame Checkpoint para esse objeto. Isso obtém um segundo instantâneo do uso de memória.

  3. Crie um terceiro CMemoryState objeto e chame sua função membro CMemoryState::Difference, fornecendo como argumentos os dois CMemoryState objetos anteriores. Se houver uma diferença entre os dois estados de memória, a Difference função retornará um valor diferente de zero. Isso indica que alguns blocos de memória não foram desalocados.

    Este exemplo mostra a aparência do código:

    // 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
    

    Observe que as instruções de verificação de memória são delimitadas por blocos #ifdef _DEBUG / #endif para que sejam compiladas somente em versões Debug do seu programa.

    Agora que você sabe que existe um vazamento de memória, pode usar outra função membro, CMemoryState::DumpStatistics, que o ajudará a localizá-lo.

Exibindo estatísticas de memória

A função CMemoryState::Difference verifica dois objetos de estado de memória e detecta quaisquer objetos que não foram desalocados corretamente do heap entre os estados inicial e final. Depois de tirar instantâneos de memória e compará-los usando CMemoryState::Difference, você pode chamar CMemoryState::DumpStatistics para obter informações sobre os objetos que não foram desalocados.

Considere o seguinte exemplo:

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

Um exemplo de dump do exemplo se parece com isso:

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

Blocos livres são blocos cuja desalocação é atrasada se afxMemDF estiver configurado como delayFreeMemDF.

Blocos de objetos comuns, mostrados na segunda linha, permanecem alocados no heap.

Os blocos que não são objetos incluem matrizes e estruturas alocadas com new. Nesse caso, quatro blocos não-objetos foram alocados no heap, mas não desalocados.

Largest number used fornece a memória máxima usada pelo programa a qualquer momento.

Total allocations fornece a quantidade total de memória usada pelo programa.

Tomando despejos de objetos

Em um programa MFC, você pode usar CMemoryState::DumpAllObjectsSince para exibir uma descrição de todos os objetos no heap que não foram desalocados. DumpAllObjectsSince despeja todos os objetos alocados desde o último CMemoryState::Checkpoint. Se nenhuma Checkpointchamada tiver ocorrido, DumpAllObjectsSince despeja todos os objetos e não objetos atualmente na memória.

Observação

Antes de poder usar o despejo de objetos MFC, você deve habilitar o rastreamento de diagnóstico.

Observação

O MFC despeja automaticamente todos os objetos vazados quando seu programa é encerrado, portanto, você não precisa criar código para despejar objetos nesse ponto.

O código a seguir testa um vazamento de memória comparando dois estados de memória e despeja todos os objetos se um vazamento for detectado.

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

O conteúdo do despejo tem esta aparência:

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

Os números entre chaves no início da maioria das linhas especificam a ordem na qual os objetos foram alocados. O objeto alocado mais recentemente tem o número mais alto e aparece no topo do despejo.

Para obter a quantidade máxima de informações de um despejo de objeto, você pode substituir a função membro Dump de qualquer objeto derivado de CObject para personalizar o despejo de objeto.

Você pode definir um ponto de interrupção em uma alocação de memória específica ao definir a variável global _afxBreakAlloc como o número mostrado nas chaves. Se você executar novamente o programa, o depurador interromperá a execução quando essa alocação ocorrer. Em seguida, você pode examinar a pilha de chamadas para ver como seu programa chegou a esse ponto.

A biblioteca de tempo de execução C tem uma função semelhante, _CrtSetBreakAlloc, que você pode usar para alocações do tempo de execução da biblioteca C.

Interpretando despejos de memória

Veja esse despejo de objeto com mais detalhes:

{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

O programa que gerou esse dump tinha apenas duas alocações explícitas—uma na pilha e outra no heap:

// 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" );

O construtor CPerson recebe três argumentos que são ponteiros para char, os quais são usados para inicializar as variáveis membro CString. No despejo de memória, você pode ver o objeto CPerson junto com três blocos de não objeto (3, 4 e 5). Eles contêm os caracteres das CString variáveis de membro e não serão excluídos quando o CPerson destruidor de objeto for invocado.

O bloco número 2 é o CPerson objeto em si. $51A4 representa o endereço do bloco e é seguido pelo conteúdo do objeto, que foram gerados por CPerson::Dump quando chamado por DumpAllObjectsSince.

Você pode adivinhar que o bloco número 1 está associado à variável de quadro CString devido ao seu número de sequência e tamanho, que corresponde ao número de caracteres na variável de quadro CString. Variáveis ​​alocadas no quadro são automaticamente desalocadas quando o quadro sai do escopo.

Variáveis de quadro

Em geral, você não deve se preocupar com objetos de heap associados a variáveis ​​de quadro porque eles são automaticamente desalocados quando as variáveis ​​de quadro saem do escopo. Para evitar confusão nos seus dumps de diagnóstico de memória, você deve posicionar suas chamadas para Checkpoint para que elas fiquem fora do escopo das variáveis ​​de quadro. Por exemplo, coloque colchetes de escopo ao redor do código de alocação anterior, conforme mostrado aqui:

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();

Com os colchetes de escopo no lugar, o despejo de memória para esse exemplo é o seguinte:

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

Alocações de não-objetos

Observe que algumas alocações são objetos (como CPerson) e algumas são alocações não-objetos. "Alocações de não-objeto" são alocações para objetos não derivados de CObject ou alocações de tipos C primitivos, como char, int, ou long. Se a classe derivada de CObject alocar espaço adicional, como para buffers internos, esses objetos exibirão tanto alocações de objetos quanto de não-objetos.

Prevenção de vazamentos de memória

Observe no código acima que o bloco de memória associado à variável de CString quadro foi desalocado automaticamente e não aparece como um vazamento de memória. A desalocação automática associada às regras de escopo cuida da maioria dos vazamentos de memória associados às variáveis ​​de quadro.

No entanto, para objetos alocados no heap, você deve excluir explicitamente o objeto para evitar um vazamento de memória. Para limpar o último vazamento de memória no exemplo anterior, exclua o CPerson objeto alocado no heap, da seguinte maneira:

{
    // 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;
}

Personalizando dumps de objetos

Ao derivar uma classe de CObject, você pode substituir a função membro Dump para fornecer informações adicionais quando usar DumpAllObjectsSince para despejar objetos na janela de saída.

A função Dump grava uma representação textual das variáveis de membro do objeto em um contexto de despejo (CDumpContext). O contexto de despejo é semelhante a um fluxo de E/S. Você pode usar o operador de acréscimo (<<) para enviar dados para um CDumpContext.

Ao substituir a função Dump, você deve primeiro chamar a versão da classe base de Dump para despejar o conteúdo do objeto da classe base. Em seguida, gere uma descrição textual e um valor para cada variável de membro da classe derivada.

A declaração da função se parece com isto: Dump

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

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

Como o despejo de objetos só faz sentido quando você está depurando seu programa, a declaração da função Dump é colocada entre colchetes com um bloco #ifdef _DEBUG / #endif.

No exemplo a seguir, a função Dump primeiro chama a função Dump de sua classe base. Em seguida, ele grava uma breve descrição de cada variável de membro junto com o valor do membro no fluxo de diagnóstico.

#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

Você deve fornecer um argumento CDumpContext para especificar o destino da saída do dump. A versão de depuração do MFC fornece um objeto predefinido CDumpContext chamado afxDump que envia a saída para o depurador.

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

Reduzindo o tamanho de uma compilação de depuração do MFC

As informações de depuração de um aplicativo MFC grande podem ocupar muito espaço em disco. Você pode usar um destes procedimentos para reduzir o tamanho:

  1. Recompile as bibliotecas MFC usando a opção /Z7, /Zi, /ZI (Formato de Informações de Depuração), em vez de /Z7. Essas opções criam um único arquivo de banco de dados de programa (PDB) que contém informações de depuração para toda a biblioteca, reduzindo a redundância e economizando espaço.

  2. Reconstruir as bibliotecas MFC sem informações de depuração (sem opção /Z7, /Zi, /ZI (Formato de Informações de Depuração). Nesse caso, a falta de informações de depuração impedirá que você use a maioria dos recursos do depurador dentro do código da biblioteca MFC, mas como as bibliotecas MFC já estão minuciosamente depuradas, isso pode não ser um problema.

  3. Crie seu próprio aplicativo com informações de depuração somente para módulos selecionados, conforme descrito abaixo.

Criando um aplicativo MFC com informações de depuração para módulos selecionados

A construção de módulos selecionados com as bibliotecas de depuração do MFC permite que você use etapas e outros recursos de depuração nesses módulos. Esse procedimento faz uso das configurações de Depuração e Lançamento do projeto, necessitando, portanto, das alterações descritas nas etapas a seguir (e também tornando necessária uma "reconstrução de tudo" quando uma compilação de Lançamento completa for necessária).

  1. No Gerenciador de Soluções, selecione o projeto .

  2. No menu Exibir , selecione Páginas de Propriedades.

  3. Primeiro, você criará uma nova configuração de projeto.

    1. Na caixa de diálogo Páginas de Propriedades do Projeto<>, clique no botão Configuration Manager.

    2. Na caixa de diálogo do Configuration Manager, localize seu projeto na grade. Na coluna Configuração , selecione <Novo...>.

    3. Na caixa de diálogo Nova Configuração do Projeto, digite um nome para sua nova configuração, como "Depuração Parcial", na caixa Nome da Configuração do Projeto .

    4. Na lista Copiar configurações de, escolha Liberar.

    5. Clique em OK para fechar a caixa de diálogo Nova Configuração do Projeto .

    6. Feche a caixa de diálogo do Configuration Manager .

  4. Agora, você definirá opções para todo o projeto.

    1. Na caixa de diálogo Páginas de Propriedades , na pasta Propriedades de Configuração , selecione a categoria Geral .

    2. Na grade de configurações do projeto, expanda Padrões do projeto (se necessário).

    3. Em Padrões do Projeto, localize o uso do MFC. A configuração atual aparece na coluna direita da grade. Clique na configuração atual e altere-a para usar o MFC em uma Biblioteca Estática.

    4. No painel esquerdo da caixa de diálogo Páginas de Propriedades , abra a pasta C/C++ e selecione Pré-processador. Na grade de propriedades, localize definições de pré-processador e substitua "NDEBUG" por "_DEBUG".

    5. No painel esquerdo da caixa de diálogo Páginas de Propriedades , abra a pasta Vinculador e selecione a Categoria de Entrada . Na grade de propriedades, localize Dependências Adicionais. Na configuração Dependências Adicionais , digite "NAFXCWD. LIB" e "LIBCMT".

    6. Clique em OK para salvar as novas opções de build e feche a caixa de diálogo Páginas de Propriedades .

  5. No menu Construir, selecione Reconstruir. Isso remove todas as informações de depuração dos seus módulos, mas não afeta a biblioteca MFC.

  6. Agora você deve adicionar informações de depuração de volta aos módulos selecionados em seu aplicativo. Lembre-se de que você pode definir pontos de interrupção e executar outras funções de depurador somente em módulos compilados com informações de depuração. Para cada arquivo de projeto no qual você deseja incluir informações de depuração, execute as seguintes etapas:

    1. No Gerenciador de Soluções, abra a pasta Arquivos de Origem localizada em seu projeto.

    2. Selecione o arquivo para o qual você deseja definir informações de depuração.

    3. No menu Exibir , selecione Páginas de Propriedades.

    4. Na caixa de diálogo Páginas de Propriedades , na pasta Configurações de Configuração , abra a pasta C/C++ e selecione a categoria Geral .

    5. Na grade de propriedades, localize Formato de informações de depuração.

    6. Clique nas configurações de Formato de Informações de Depuração e selecione a opção desejada (geralmente /ZI) para obter informações de depuração.

    7. Se você estiver usando um aplicativo gerado pelo assistente de aplicativo ou tiver cabeçalhos pré-compilados, será necessário desativar os cabeçalhos pré-compilados ou recompilá-los antes de compilar os outros módulos. Caso contrário, você receberá o aviso C4650 e a mensagem de erro C2855. Você pode desativar cabeçalhos pré-compilados alterando a configuração Criar/Usar Cabeçalhos Pré-compilados na caixa de diálogo Propriedades do <Projeto> (pasta Propriedades de Configuração, subpasta C/C++, categoria Cabeçalhos Pré-compilados).

  7. No menu Compilar , selecione Compilar para recompilar arquivos de projeto desatualizados.

    Como alternativa à técnica descrita neste tópico, você pode usar um makefile externo para definir opções individuais para cada arquivo. Nesse caso, para vincular às bibliotecas de depuração do MFC, você deve definir o sinalizador de _DEBUG para cada módulo. Se você quiser usar bibliotecas de versão do MFC, deverá definir NDEBUG. Para obter mais informações sobre como escrever makefiles externos, consulte a Referência do NMAKE.