Olá, leitores! Wow! Sejam bem-vindos ao novo visual do blog! 🙂
Bom, como já faz um tempo que não elaboro artigos sobre Delphi, hoje é dia de publicar mais um tópico técnico! Imagine que você esteja realizando alguns testes funcionais no seu software, quando, de repente, ocorre um Access Violation! Você sabe como proceder para rastrear (e resolver) o problema?
O tal do Access Violation é um dilema para qualquer programador Delphi e virou até motivo de piada. Alguns ironizam, dizendo “Acesso violento” ou “Excel Violation”, haha. O problema é que, por ser uma mensagem de erro muito genérica, os Access Violations realmente nos deixam de cabelo em pé, principalmente em situações quando parece não ter um motivo aparente. A saída é respirar fundo e fazer uso de alguns recursos do Delphi para identificar a linha de código que está causando a violação.
Call Stack
Pois bem, e por falar em recursos, o Delphi disponibiliza uma ferramenta que captura a pilha de chamadas de métodos e nos ajuda bastante nessa rastreabilidade, chamada Call Stack.
Já ouviu falar? Não? Então, na próxima vez que você se deparar com um Access Violation, acesse o menu View > Debug Windows > Call Stack e observe a janela que será exibida. É uma lista que representa um “histórico” de chamadas de métodos, permitindo acompanhar o fluxo de operações que o sistema está executando até o levantamento da exceção. Confira o exemplo abaixo:
Ao analisar o Call Stack acima, é possível identificar que tudo se inicia no evento de clique de um botão e percorre vários outros métodos, de baixo para cima, até a parada do sistema. Ao clicar duas vezes no nome dos métodos exibidos na janela, o Delphi nos leva à linha de código que os chamam. Opa, assim fica mais simples rastrear o erro, não é?
Baixe o exemplo neste link, execute-o no Delphi e observe, pelo Call Stack, que a origem do problema está no método ConsultarDados
.
Por que neste método?
Os erros de Access Violation, em grande maioria, são causados por acessos a endereços de memória que não existem, como, por exemplo, utilizar um objeto que não foi instanciado ou que já foi destruído. Considere o código abaixo:
1 2 3 4 5 6 7 8 9 |
var ListaCidades: TStringList; begin ListaCidades.Add('São Paulo'); ListaCidades.Add('Maringá'); ListaCidades.Add('Florianópolis'); ... Memo1.Lines.Text := ListaCidades.Text; end; |
Ao executar esse código, o que acontece? Access Violation! O motivo do erro é visível: o objeto do tipo TStringList
não foi instanciado antes de ser utilizado, ou seja, esse objeto não existe na memória e não pode ser referenciado. Como solução, se adicionarmos a linha abaixo no início do método, o erro será solucionado:
1 |
ListaCidades := TStringList.Create; |
Agora, uma reflexão: quando usamos uma ferramenta, como uma chave de fenda, o correto é devolvê-la no mesmo lugar onde estava, certo? Isso é bom senso. Pensando assim, já que criamos um objeto, também precisamos destruí-lo após a utilização, caso contrário, o espaço alocado na memória para esse objeto permanecerá preenchido. Isso pode causar quedas de desempenho, uso alto de memória RAM, Memory Leaks, ou, talvez, os erros de “Out of Memory”.
A destruição de objetos no Delphi pode ser realizada com o FreeAndNil
:
1 |
FreeAndNil(ListaCidades); |
Mas, cuidado! Após destruir o objeto, ele é liberado da memória e não deve mais ser utilizado. Como exemplo, o código abaixo também resultaria em um Access Violation:
1 2 3 4 |
... FreeAndNil(ListaCidades); Memo1.Text := ListaCidades.Text; // neste momento, o objeto não existe mais |
Como minimizar os Access Violations?
Embora seja um erro de runtime, ou seja, que ocorre somente em tempo de execução, existem algumas técnicas de codificação que podem reduzir o risco da apresentação de Access Violations no sistema.
A primeira delas é o Assigned
– função que identifica se o objeto passado por parâmetro está alocado na memória. Ao utilizá-lo, podemos evitar que uma operação com um objeto inexistente seja executada. No código a seguir, só obtenho as informações da lista caso o objeto exista:
1 2 |
if Assigned(ListaCidades) then Memo1.Lines.Text := ListaCidades.Text; |
Claro, é importante mencionar que não é nada apropriado utilizar o Assigned
em todos os lugares que fazem referência a objetos, já que tornaria o código redundante e desnecessário. Utilize-o em pontos estratégicos, como em métodos que são chamados por vários locais no software. Um destes locais, talvez, pode não utilizar os objetos internos do método, portanto, se não utilizarmos o Assigned
para verificar a existência, ocorrerá os erros de violação de acesso.
A segunda técnica é o esqueleto do Try/Finally
, no qual já comentei no artigo sobre técnicas de tratamento de exceções. Adquira o hábito de escrever a estrutura completa do Try/Finally
antes de partir para a codificação da regra do método. Além de garantir a destruição do objeto, esse hábito colabora para a confiabilidade do aplicativo e evita eventuais Access Violations, caso o objeto seja uma variável de classe.
1 2 3 4 5 6 7 8 |
begin ListaCidades := TStringList.Create; try finally FreeAndNil(ListaCidades); end; end; |
A terceira técnica é a utilização do destructor
da classe TObject
para liberar todos objetos que foram instanciados, visando o desempenho e integridade do sistema. Atente-se que o destructor também é útil para desligar eventos, redefinir propriedades e desvincular Interfaces, quando necessário. Este método deve ser declarado como override
(sobrescrito) na seção public da classe:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public desctructor Destroy; override; ... destructor TForm1.Destroy; begin // desliga eventos DataSet1.AfterPost := nil; // desvincula Interfaces IControlador := nil; // destrói objetos instanciados na classe FreeAndNil(ObjetoClasse1); FreeAndNil(ObjetoClasse2); // redefine propriedades ClasseConfiguracoes.AtivarLog := True; // invoca os procedimentos da classe base inherited; end; |
Na minha opinião, a atividade de instanciar e destruir os objetos de forma apropriada (e consciente) é um diferencial de um desenvolvedor, que, na verdade, não deixa de ser uma questão de pura responsabilidade.
Aproveitando o contexto deste artigo, vale apresentar o EurekaLog, um componente para Delphi que captura exceções no software e elabora um log muito bem detalhado, facilitando ainda mais a rastreabilidade do código defeituoso. Além disso, é possível configurá-lo para enviar o log para o e-mail do desenvolvedor no momento em que a exceção ocorre. Este envio pode ser ajustado para utilizar uma thread separada, dispensando a intervenção do usuário.
Obrigado pela atenção, leitores!
Abraço!
Boa tarde André, primeiramente gostaria de lhe dar os parabens pelo blog, todos os artigos que li são muito bons e queria aproveitar para lhe pedir (se for possivel, é claro) um artigo sobre conexoes via socket em delphi. Sou programador junior e estou com algumas duvidas principalmente no tratamento de conexoes do lado do servidor. desde já agradeço pela atenção.
Olá, Leonardo!
Obrigado pelo feedback sobre o blog! Isso me motiva bastante para continuar escrevendo!
Leonardo, eu já tenho uma pauta de temas para escrever na sequência mas, mesmo assim, posso adicionar o tópico de sockets no Delphi.
Assim que possível, elaboro um artigo!
Abraço!
Parabéns, um excelente artigo!
Agora ficou mais fácil resolver esses acessos violentos rs.
Att,
Opa, obrigado pelo comentário, João Leno!
Vamos exterminar os acessos violentos! rsrs
Está dando “Access Violation” na hora que crio a instância. Alguma dica do que pode ser? Grato desde já pelas dicas e atenção.
Olá, Robertson, tudo bem? Obrigado pelo comentário.
Se o Access Violation ocorre ao criar o objeto, o erro provavelmente está no construtor (create) da classe TDocumentoPAFFichaNGC.
Se você tem conhecimento em depuração (debug) no Delphi, adicione um breakpoint no construtor dessa classe e acompanhe a execução de cada linha até o erro ocorrer. Dessa forma ficará mais fácil identificar o problema.
Abraço!
Boa tarde, gostei do POST, porém eu tenho este erro constantemente em alguns clientes, e trechos que não são meus, por exemplo Error reading Labelx.Caption . “Access violation”…. É apenas um label no form, com formatação padrão, sem funções nem evento, e sem nenhuma mudança em runtime.
Olá, Márcio, tudo certo?
Este erro parece estar relacionado com o arquivo DFM, que armazena a definição das propriedades dos componentes inseridos no formulário. Já encontrei erros semelhantes quando o projeto foi migrado de uma versão muito antiga do Delphi para uma versão mais nova. Em outros casos, o arquivo DFM estava corrompido e foi necessário uma restauração.
Você notou se esse erro ocorre em apenas um formulário? Se possível, envie um e-mail para [email protected] com mais detalhes, como a versão do Delphi.
Fico no aguardo. Abraço!
Seu exemplo não funcionou, deu erro:
[Error] AccessViolation.dpr(11): Undeclared identifier: ‘MainFormOnTaskbar’ e não permite ver a call stack
Olá, Luciano.
Acredito que você está tentando abrir este projeto no Delphi 7 ou inferior.
O projeto foi desenvolvido em uma versão mais recente do Delphi (Seattle, pelo que eu me lembre).
De qualquer forma, para testar este exemplo na versão que você está utilizando, basta remover a linha abaixo do arquivo DPR:
“View > Debug Windows > Call Stack”
Show esta função do Delphi. Não conhecia e me salvou hoje.
Valeu demais.
Legal, Pierry. Use e abuse desse recurso! 🙂
Abraço!
André, eu ativei essa função “View > Debug Windows > Call Stack”, porém quando rodo não mostra nada na janela. Tem alguma configuração a mais a ser feita?
Olá, Frederico.
Acesse o menu Project > Options e depois em Compiling. Verifique se a opção “Use debug .dcus” está selecionada.
Além disso, vale lembrar que o Call Stack só irá apresentar informações se o projeto estiver sendo executado em modo de depuração (F9) e também se a opção “Debug” estiver marcada em Build Configurations.
Abraço!
Bom dia André,
Segui com atenção V. exemplo. Concretamente estou a referir-me ao “Access Violation” e aos passos para procurar identificar as suas causas: View-> Debug -> Windows-> Call Stack; tudo como afirma, porém, a janela a seguir não abre! Baixei a última versão do EUREKALOG, na esperança que ajudasse algo mais, mas não consegui grande coisa! Apenas dá para perceber que há um erro, provavelmente, um bug no UserControl que pendura o meu programa na hora da saída.
Com o Eurekalog apenas dá para perceber que se trata de um problema lá com First chance exception at $0040ADE5. Exception class $C0000005 with message ‘access violation at 0x0040ade5: read of address 0x06928384’.
Olá, Mário, tudo bem?
Bom, eu precisaria analisar melhor o contexto do código, mas, pela mensagem de erro, aparentemente há um objeto inexistente sendo acessado. Isso normalmente ocorre quando tenta-se acessar um objeto que já foi destruído.
Fiquei curioso quando ao Call Stack. Essa janela deveria mostrar as informações das chamadas. Não sei o que houve para a janela não aparecer para você.
Mário, experimente inserir breakpoints no código e descobrir exatamente qual a linha que está gerando o erro. Isso irá ajudá-lo na identificação do problema.
Abraço!
Tenho um sistema em Delphi, e preciso de uma ajuda. Usando o programa Process Explorer vejo que existe uma thread que está consumindo muita memoria com picos de 1 segundo.
Exemplo: 100MB de consumo, 1 segundo depois sobe para 500MB e 1 segundo depois volta aos 100MB, porém ao verificar qual é a thread ele não exibe no LOG. Quando dou um kill ou suspend a threads o consumo fica estável.
É possível colocar algo que poderia mostrar essa threads que não é exibida no log?
Olá, Lucio, bom dia.
Infelizmente tenho pouco conhecimento com o Process Explorer, portanto, eu tentaria encontrar esse problema do consumo através de depuração (debugging). Você sabe qual é a classe thread que está executando esse processamento? Se souber, experimente adicionar alguns breakpoints e acompanhar a execução dos métodos da thread. Talvez há uma falha no gerenciamento de memória, como Memory Leaks, leituras excessivas de I/O ou um grande volume de dados em memória.
Outra alternativa é utilizar o relatório de Memory Leaks nativo do Delphi, adicionando a linha abaixo no arquivo de projeto (DPR):
Ao fechar a aplicação, o Delphi irá exibir uma mensagem com os possíveis vazamentos de memória que ocorreram durante a execução. Este relatório pode ajudá-lo a rastrear o “culpado” por esse pico de processamento.
Abraço!
Bom dia Andre, tenho uma consulta que utilizando o Run , dá Acess Violation, mas utilizando o executável não ocorre. fazendo um debug descobri a parte do programa que dispara esse Acess Violation, porém não consigo acertar. conseguiria me dar uma ajuda ?
a parte do programa é esse abaixo:
// gera arq sortado pela chave
Recs:= Arq.CONDREL.RecordCount; // quantidade de registros
Arq.CONDREL.first; // a partir do primeiro
CaseIns:= True;
Field := Arq.CONDREL.fieldbyname(‘chave’).Index+1;
ret:= DbiSortTable(Arq.CONDREL.dbHandle,
nil,
nil,
Arq.CONDREL.Handle,
nil,
nil,
Arq.CONDREL2.Handle,
1, @Field, @CaseIns, nil, nil, False, nil, Recs);
if ret = dbierr_invalidhndl then MENS(‘handle invalido’,2);
if ret = dbierr_invalidfilename then MENS(‘falta source table’,2);
if ret = dbierr_unknowntbltype then MENS(‘tipo invalido’,2);
if ret = dbierr_invalidparam then MENS(‘num. campos invalido’,2);
e a mensagem que recebo quando uso F9(RUN) é a seguinte
Project Cond_Rel.exe raised exception class EAccessViolation with message ‘Acess violation at address 4BE4BC9B in module ‘IDAPI32.DLL’.
Read of address 492CD7C1. Process stoped
obrigado pela ajuda
Fala, Robson, boa tarde.
Tudo indica que a aplicação não está tratando a exceção.
Olhando o código, notei que pode ser diferentes possíveis causas, desde um objeto não instanciado até handles inválidos. Access Violations geralmente ocorrem quando a aplicação tenta acessar um endereço na memória não existente. Além disso, infelizmente não tenho conhecimento da função DbiSortTable.
Para descobrir exatamente o problema, é necessário fazer a depuração do código e testar cada variável e parâmetro desse código. Por exemplo, a variável “Field” ou o parâmetro “dbHandle” pode estar nil.
Abraço!