Partilhar via


Técnicas de depuração do MFC

Ao depurar um programa MFC, estas técnicas de depuração podem ser úteis.

AfxDebugBreak

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

AfxDebugBreak( );

Em plataformas Intel, AfxDebugBreak gera o seguinte código, que se quebra no código-fonte em vez de no código de núcleo.

_asm int 3

Em outras plataformas, AfxDebugBreak apenas chama DebugBreak.

Certifique-se de remover as instruções AfxDebugBreak ao criar uma compilação de lançamento ou use #ifdef _DEBUG para as envolver.

A macro TRACE

Para exibir mensagens do seu programa na janela Saída do depurador, você pode usar a macro ATLTRACE ou a macro MFC TRACE . Como 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 . Como 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 lida adequadamente com os parâmetros char* e wchar_t*. Os exemplos a seguir demonstram o uso da macro TRACE juntamente 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.

Detetando vazamentos de memória no MFC

MFC fornece classes e funções para detetar memória que é alocada, mas nunca deslocalizada.

Rastreando alocações de memória

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

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

#define new DEBUG_NEW

Quando você faz um despejo de objeto, cada objeto alocado com DEBUG_NEW mostrará o arquivo e o número da linha onde ele foi alocado, permitindo que você identifique 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 do DEBUG_NEW, você deve usar DEBUG_NEW explicitamente ou #define novo , como mostrado acima.

Ativando diagnósticos de memória

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

Para ativar ou desativar o diagnóstico de memória

  • Chame a função global AfxEnableMemoryTracking para ativar ou desativar o alocador de memória de diagnóstico. Como os diagnósticos de memória estão ativados 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 desejar um controle mais preciso sobre os recursos de diagnóstico de memória, você pode ativar e desativar seletivamente os recursos de diagnóstico de memória individuais definindo o valor da variável global 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).
    delayFreeMemDF Adie a libertação de memória ao chamar delete ou free até que o programa termine. Isso fará com que seu programa aloque a quantidade máxima possível de memória.
    checkAlwaysMemDF Chame AfxCheckMemory sempre que a memória for alocada ou liberada.

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

    afxMemDF = allocMemDF | delayFreeMemDF | checkAlwaysMemDF;
    

Tirar 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. Após o programa executar suas operações de alocação e desalocação de memória, crie outro CMemoryState objeto e chame Checkpoint para esse objeto. Isso obtém um segundo instantâneo do uso da memória.

  3. Crie um terceiro CMemoryState objeto e chame a sua função de membro CMemoryState::Difference, fornecendo como argumentos os dois objetos anteriores CMemoryState. 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 agrupadas por blocos #ifdef _DEBUG / #endif para que sejam compiladas apenas em versões de depuração do seu programa.

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

Visualizando estatísticas de memória

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

Considere o seguinte exemplo:

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

Um exemplo de dump tem esta aparência:

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 foi definida como delayFreeMemDF.

Os 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. Neste caso, quatro blocos não-objeto foram alocados no heap, mas não desalocados.

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

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

Levando despejos de objetos

Em um programa MFC, pode usar CMemoryState::DumpAllObjectsSince para listar uma descrição de todos os objetos na pilha que não foram desalocados. DumpAllObjectsSince despeja todos os objetos alocados desde o último CMemoryState::Checkpoint. Se nenhuma Checkpoint chamada tiver ocorrido, DumpAllObjectsSince despejará todos os objetos e não objetos atualmente na memória.

Observação

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

Observação

MFC despeja automaticamente todos os objetos vazados quando o programa é encerrado, então 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 detetado.

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 em que os objetos foram alocados. O objeto alocado mais recentemente tem o maior número e aparece na parte superior do dump.

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

Você pode definir um ponto de interrupção em uma alocação de memória específica definindo a variável _afxBreakAlloc global como o número mostrado nas chaves. Se você executar novamente o programa, o depurador interromperá a execução quando essa alocação ocorrer. Você pode então olhar para 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 de tempo de execução C.

Interpretando despejos de memória

Observe este 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 montão:

// 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 CPerson construtor usa três argumentos que são ponteiros para char, que são usados para inicializar CString variáveis de membro. No despejo de memória, você pode ver o CPerson objeto junto com três blocos não objeto (3, 4 e 5). Eles mantêm os caracteres para as CString variáveis de membro e não serão excluídos quando o destruidor de CPerson objeto for invocado.

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

Pode-se deduzir que o bloco número 1 está associado à variável CString do quadro por causa do seu número sequencial e tamanho, que correspondem ao número de caracteres na variável CString do quadro. As 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 pilha associados a variáveis de quadro porque eles são automaticamente desalocados quando as variáveis de quadro saem do escopo. Para evitar a confusão nos seus registos de diagnóstico de memória, deves posicionar as tuas chamadas para Checkpoint de forma a que fiquem fora do escopo das variáveis de quadro. Por exemplo, coloque colchetes de escopo em 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 registo de memória para este exemplo é o que se segue:

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 outras são alocações não objetos. "Alocações de não objetos" são alocações para objetos não derivados de CObject ou alocações de tipos C primitivos, como char, intou long. Se a classe derivada de CObject alocar espaço adicional, como para buffers internos, esses objetos exibirão alocações tanto de objeto quanto de não objeto.

Prevenção de fugas de memória

Observe no código acima que o bloco de memória associado à variável de quadro foi desalocado CString 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 a variáveis de quadro.

Para objetos alocados na pilha, no entanto, 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 na pilha, 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 despejos de objetos

Quando você deriva uma classe de CObject, você pode substituir a Dump função de membro para fornecer informações adicionais quando você usa DumpAllObjectsSince para despejar objetos para a janela Saída.

A Dump função grava uma representação textual das variáveis 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 append (<<) para enviar dados a um CDumpContext.

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

A declaração da função é assim: 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 objeto só faz sentido quando você está depurando seu programa, a Dump declaração da função é colocada entre colchetes com um bloco #ifdef _DEBUG / #endif .

No exemplo seguinte, a função Dump chama primeiro a função Dump da sua classe base. Em seguida, ele grava uma breve descrição de cada variável de membro, juntamente com o valor do membro, para o 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 CDumpContext argumento para especificar para onde o resultado do despejo irá. 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 MFC

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

  1. Reconstrua as bibliotecas MFC usando a opção /Z7, /Zi, /ZI (Debug Information Format), 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. Reconstrua as bibliotecas MFC sem a opção de incluir informações de depuração (sem /Z7, /Zi, /ZI (Formato de Informação 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 completamente depuradas, isso pode não ser um problema.

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

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

A criação de módulos selecionados com as bibliotecas de depuração MFC permite utilizar o passo-a-passo e os outros recursos de depuração nesses módulos. Este procedimento faz uso das configurações Debug e Release do projeto, necessitando assim das alterações descritas nas etapas a seguir (e também tornando necessário um "rebuild all" quando uma compilação completa é 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 Gerenciador de Configurações.

    2. Na caixa de diálogo 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 a 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 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 Uso do MFC. A configuração atual aparece na coluna da direita da grade. Clique na configuração atual e altere-a para Usar 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 compilação e fechar a caixa de diálogo Páginas de Propriedades .

  5. No menu Compilar , selecione Reconstruir. Isso remove todas as informações de depuração dos 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 deseja definir as 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 Definições de Configuração , abra a pasta C/C++ e selecione a categoria Geral .

    5. Na grade de propriedades, localize Debug Information Format.

    6. Clique nas configurações de Debug Information Format 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á aviso C4650 e 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 reconstruir 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 com as bibliotecas de depuração MFC, você deve definir o sinalizador de _DEBUG para cada módulo. Para utilizar as bibliotecas de distribuição MFC, deve definir NDEBUG. Para obter mais informações sobre como escrever makefiles externos, consulte a Referência NMAKE.