Técnicas de depuração MFC
Se você estiver depurando um programa MFC, essas técnicas de depuração poderão ser úteis.
Neste tópico
Detectando vazamentos de memória no MFC
AfxDebugBreak
O MFC fornece uma função AfxDebugBreak especial para evitar a codificação de pontos de interrupção no código-fonte:
AfxDebugBreak( );
Em plataformas Intel, o AfxDebugBreak
produz o código a seguir, que é interrompido no código-fonte em vez de no código kernel:
_asm int 3
Em outras plataformas, o AfxDebugBreak
simplesmente chama o DebugBreak
.
Não se esqueça de remover as instruções AfxDebugBreak
ao criar uma compilação da versão ou usar as #ifdef _DEBUG
para delimitá-los.
{1>A macro TRACE<1}
Para exibir mensagens do seu programa no depurador janela de Saída, você pode usar a macro ATLTRACE ou a macro TRACE do MFC. Assim como as asserções, as macros de rastreamento estão ativas somente na versão de depuração do programa e desaparecem quando compiladas na versão de lançamento.
Os exemplos a seguir mostram algumas das formas que você pode usar a macro TRACE. Como o printf
, a macro TRACE pode lidar com um número de 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 trata adequadamente os parâmetros char* e wchar_t*. Os exemplos a seguir demonstram o uso da macro TRACE junto com os 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, confira Serviços de diagnóstico.
Detectando vazamentos de memória no MFC
O MFC fornece classes e funções para detectar a memória alocada, mas nunca desalocada.
Acompanhando alocações de memória
No MFC, você pode usar a macro DEBUG_NEW no lugar do operador new para ajudar a localizar possíveis vazamentos de memória. Na versão de depuração do programa, o DEBUG_NEW
controla o nome de arquivo e o número de linha para cada objeto que aloca. Quando você compila uma versão de lançamento do programa, o DEBUG_NEW
resolve-se como uma operação new simples sem as informações de nome do arquivo e número de linha. Dessa forma, você não paga penalidade de velocidade da versão de lançamento do programa.
Se você não quiser reescrever o programa inteiro para usar DEBUG_NEW
em vez de new, poderá definir esta macro em seus arquivos de origem:
#define new DEBUG_NEW
Quando você faz despejo de objeto, cada objeto alocado com DEBUG_NEW
mostrará o arquivo e o número da linha em que foi alocado, permitindo que você localize as fontes de possíveis 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 o DEBUG_NEW
explicitamente ou #define new como mostrado acima.
Habilitando diagnóstico de memória
Para que você possa usar os recursos de diagnóstico de memória, deverá 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 diagnóstico de memória. Como o diagnóstico de memória é ativado por padrão na biblioteca de depuração, você normalmente usará essa função para desativá-la temporariamente, o que aumenta a velocidade de execução do programa e reduz a saída de diagnóstico.
Para selecionar recursos de diagnóstico de memória específicos com afxMemDF
Se você quiser um controle mais preciso sobre os recursos de diagnóstico de memória, poderá habilitar e desabilitar seletivamente os recursos individuais de diagnóstico de memória definindo o valor da variável global afxMemDF do MFC. Essa variável pode ter os seguintes valores conforme especificado pelo tipo enumerado afxMemDF.
Valor Descrição allocMemDF Ativar o alocador de diagnóstico de memória (padrão). delayFreeMemDF Atrase a liberação de memória ao chamar delete
ou atéfree
até o programa fechar. Isso fará o programa alocar a quantidade máxima de memória possível.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 OR lógica, como mostrado a seguir:
afxMemDF = allocMemDF | delayFreeMemDF | checkAlwaysMemDF;
Tirando instantâneos de memória
Crie um objeto CMemoryState e chame a função de membro CMemoryState::Checkpoint. Isso cria o primeiro instantâneo de memória.
Depois que seu programa executar as operações de alocação e desalocação de memória, crie outro objeto
CMemoryState
e chameCheckpoint
para esse objeto. Isso obtém um segundo instantâneo do uso da memória.Crie um terceiro objeto
CMemoryState
e chame sua função de membro CMemoryState::Difference, fornecendo como argumentos os dois objetosCMemoryState
anteriores. Se houver uma diferença entre os dois estados da memória, a funçãoDifference
retornará um valor diferente de zero. Isso indica que alguns blocos de memória não foram desalocados.Esse exemplo mostra a aparência deste 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 envolvidas por blocos #ifdef _DEBUG / #endif de modo que sejam compiladas apenas em versões de depuração do seu programa.
Agora que você sabe que existe um perda de memória, pode usar outra função de membro, CMemoryState::DumpStatistics, que o ajudará a localizá-la.
Exibindo estatísticas de memória
A função CMemoryState::Difference observa dois objetos de estado de memória e detecta todos os objetos não desalocados do heap entre os estados de início e fim. Depois que você tiver obtido instantâneos de memória e comparado-os usando CMemoryState::Difference
, poderá chamar CMemoryState::DumpStatistics para obter informações sobre os objetos que não foram desalocados.
Considere o exemplo a seguir:
if( diffMemState.Difference( oldMemState, newMemState ) )
{
TRACE( "Memory leaked!\n" );
diffMemState.DumpStatistics();
}
Um despejo de exemplo tem a seguinte 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
Os blocos livres são os blocos cuja desalocação será atrasada se afxMemDF
tiver sido definido como delayFreeMemDF
.
Os blocos de objeto comuns, mostrados na segunda linha, permanecem alocados no heap.
Os blocos diferentes de objeto incluem as matrizes e as estruturas alocadas com new
. Nesse caso, quatro blocos diferentes de 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.
Obtendo despejos de objeto
Em um programa MFC, você pode usar CMemoryState::DumpAllObjectsSince para despejar uma descrição de todos os objetos no heap que não foram desalocados. O DumpAllObjectsSince
despeja todos os objetos alocados desde o último CMemoryState::Checkpoint. Se nenhuma chamada de Checkpoint
tiver ocorrido, o DumpAllObjectsSince
despejará todos os objetos e não objetos atualmente na memória.
Observação
Antes de usar o despejo de objeto do MFC, você deverá habilitar o rastreamento de diagnóstico.
Observação
O MFC despeja automaticamente todos os objetos vazados quando o programa sair, portanto você não precisará criar o código para despejar objetos nesse momento.
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 é semelhante ao 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
{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 alto do despejo.
Para obter a quantidade máxima de informações fora de um despejo de objeto, você poderá substituir a função de membro Dump
de qualquer objeto derivado de CObject
para personalizar o despejo do objeto.
Você pode definir um ponto de interrupção em uma alocação de memória específica configurando a variável global _afxBreakAlloc
com o número mostrado entre chaves. Se você executar novamente o programa, o depurador interromperá a execução quando essa alocação ocorrer. Você pode em seguida analisar a pilha de chamadas para ver como o programa chegou a esse ponto.
A biblioteca em tempo de execução C tem uma função semelhante, _CrtSetBreakAlloc, que você pode usar para alocações em 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 este despejo tinha apenas duas alocações explícitas- uma na pilha e uma 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
usa três argumentos que são os ponteiros para char
, que são usados para inicializar variáveis de membro CString
. No despejo de memória, você pode ver o objeto CPerson
junto com três blocos diferentes de objeto (3, 4 e 5). Eles mantêm os caracteres para as variáveis de membro CString
e não serão excluídos quando o destruidor do objeto CPerson
for invocado.
O bloco número 2 é o próprio objeto CPerson
. $51A4
representa o endereço do bloco e é seguido pelos conteúdos do objeto, que eram gerados pelo CPerson
::Dump
quando chamados pelo DumpAllObjectsSince.
Você pode determinar a qual bloco o número 1 está associado com a variável de quadro CString
devido ao número e ao tamanho da sequência, que corresponde ao número de caracteres na variável CString
do quadro. As variáveis alocadas no quadro são desalocadas automaticamente quando o quadro sai do escopo.
Variáveis de quadro
Em geral, você não precisa se preocupar com objetos heap associados a variáveis do quadro porque eles são desalocados automaticamente quando as variáveis do quadro saem do escopo. Para evitar a confusão nos despejos de diagnóstico de memória, você deve posicionar suas chamadas para Checkpoint
de modo que fiquem fora do escopo de variáveis do quadro. Por exemplo, coloque colchetes de escopo em volta do código de alocação anterior, como mostrado a seguir:
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 este exemplo ocorre da seguinte maneira:
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 diferentes de objeto
Observe que algumas alocações são objetos (como CPerson
) e algumas são alocações diferentes de objeto. As "alocações diferentes de objeto" são alocações para os objetos não derivados de CObject
ou alocações de tipos C primitivos como char
, int
ou long
. Se a classe derivada de CObject aloca o espaço adicional, por exemplo, para buffers internos, esses objetos mostrarão as alocações de objeto e diferentes de objeto.
Evitando vazamentos de memória
Observe no código acima que o bloco de memória associado à variável do quadro CString
foi desalocado automaticamente e não aparece como um vazamento de memória. A desalocação automática associada a regras de escopo é responsável pela maioria dos vazamentos de memória associados a variáveis do quadro.
Para os objetos alocados no heap, 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 objeto CPerson
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 despejos de objeto
Quando você deriva uma classe de CObject, pode substituir a função de membro Dump
para fornecer informações adicionais quando usa DumpAllObjectsSince para despejar objetos para a 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
.
Quando você substitui a função Dump
, primeiro deve chamar a versão da classe base do Dump
para despejar o conteúdo do objeto da classe base. Em seguida, gere uma descrição e um valor textuais para cada variável de membro da sua classe derivada.
A declaração da função Dump
é semelhante a esta:
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 objeto de despejo só faz sentido quando você estiver depurando seu programa, a declaração da função Dump
é envolvida com um bloco #ifdef _DEBUG / #endif.
No exemplo a seguir, a função Dump
primeiro chama a função Dump
para a sua classe base. Em seguida, ela 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 de CDumpContext
para especificar para onde a saída de despejo irá. A versão de depuração do MFC fornece um objeto CDumpContext
predefinido 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 para um aplicativo MFC grande pode utilizar muito espaço em disco. Você pode usar um destes procedimentos para reduzir o tamanho:
Recompile as bibliotecas do 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 do programa (PDB) que contém informações de depuração para a biblioteca inteira, reduzindo a redundância e economizando espaço.
Recompile as bibliotecas do MFC sem informações de depuração (sem a opção /Z7, /Zi, /ZI (Formato de Informações de Depuração). Nesse caso, a falta de informações de depuração impedirão que você use a maioria dos recursos do depurador no código da biblioteca MFC, mas como as bibliotecas MFC já estão completamente depuradas, isso pode não ser um problema.
Crie seu próprio aplicativo com informações de depuração para os módulos selecionados apenas como descrito abaixo.
Criando um aplicativo do MFC com informações de depuração para os módulos selecionados
Criar os módulos selecionados com as bibliotecas de depuração MFC permite usar a depuração e outros recursos nesses módulos. Esse procedimento utiliza as configurações de Depuração e Versão do projeto, necessitando assum das alterações descritas nas etapas a seguir (e também fazendo uma operação de "recompilar tudo” quando uma compilação de liberação completa for necessária).
No Gerenciador de Soluções, selecione o projeto.
No menu Exibir, selecione Páginas de Propriedades.
Primeiro, você criará uma nova configuração de projeto.
Na caixa de diálogo <Projeto> Páginas de Propriedades, clique no botão Configuration Manager.
Na caixa de diálogo do Configuration Manager, localize seu projeto na grade. Na coluna Configuração, selecione <Novo…>.
Na caixa de diálogo Nova Configuração de Projeto, digite um nome para a nova configuração, por exemplo “Depuração parcial”, na caixa Nome de Configuração do Projeto.
Na lista Copiar Configurações de, escolha Versão.
Clique em OK para fechar a caixa de diálogo Nova Configuração de Projeto.
Feche a caixa de diálogo Configuration Manager.
Agora, você definirá opções para o projeto inteiro.
Na caixa de diálogo Páginas de Propriedades, na pasta Propriedades de Configuração, selecione a categoria Geral.
Na grade de configurações de projeto, expanda Padrões de Projeto (se necessário).
Em Padrões de Projeto, localize Uso do MFC. A configuração atual é exibida na coluna direita da grade. Clique na configuração atual e altere-a para Usar MFC em uma Biblioteca Estática.
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 do Pré-processador e substitua "NDEBUG" por "_DEBUG".
No painel esquerdo da caixa de diálogo Páginas de Propriedades, abra a pasta Vinculador e selecione a categoria Entrada. Na grade de propriedades, localize Dependências Adicionais. Na definição de Dependências Adicionais, digite "NAFXCWD.LIB" e "LIBCMT".
Clique em OK para salvar as novas opções de criação e feche a caixa de diálogo Páginas de Propriedades.
No menu Compilar, selecione Recompilar. Isso remove todas as informações de depuração de seus módulos, mas não afeta a biblioteca MFC.
Agora você deve adicionar as informações de depuração de volta para os módulos selecionados em seu aplicativo. Lembre-se de que você pode definir pontos de interrupção e executar outras funções de depurador apenas nos módulos que você compilou com informações de depuração. Para cada arquivo de projeto em que você quer incluir informações de depuração, execute as seguintes etapas:
No Gerenciador de Soluções, abra a pasta Arquivos de Origem localizada em seu projeto.
Selecione o arquivo para o qual você quer definir informações de depuração.
No menu Exibir, selecione Páginas de Propriedades.
Na caixa de diálogo Páginas de Propriedades, na pasta Definições de Configuração, abra a pasta C/C++ e, em seguida, selecione a categoria Geral.
Na grade de propriedades, localize Formato de Informação de Depuração.
Clique nas configurações de Formato de Informação de Depuração e selecione a opção desejada (normalmente /ZI) para obter as informações de depuração.
Se você estiver usando um aplicativo gerado por assistente ou se tiver cabeçalhos pré-compilado, será preciso 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 mensagens de erro C2855. Você pode desligar cabeçalhos pré-compilados alterando a configuração Criar/Usar Cabeçalho Pré-Compilado na caixa de diálogo <Projeto> Propriedades (pasta Propriedades de Configuração, subpasta C/C++, categoria Cabeçalhos Pré-compilados).
No menu Compilar, selecione Compilar para recompilar os arquivos de projeto que estão 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ê deverá definir o sinalizador _DEBUG para cada módulo. Se você quiser usar bibliotecas de versão MFC, deverá definir NDEBUG. Para obter mais informações sobre como escrever makefiles externos, confira Referência de NMAKE.