Previna AV com Assert

Uma das razões mais comuns para acontecer Access Violations (AV) nos programas é quanto se tenta acessar um método ou uma propriedade de um objeto que ainda não foi alocado ou que já foi desalocado da memória.

Por exemplo:

procedure AV;
var
  List: TStringList;
begin
  // AV porque List é um objeto não alocado
  List.Add('teste');
 
  // ok, porque List foi previamente alocado
  List := TStringList.Create;
  List.Add('novamente');
 
  // AV, porque o objeto foi desalocado e o valor
  // da variável List é um ponteiro inválido
  List.Free;
  List.Add('outra vez');
 
  // AV da mesma forma que antes, apenas a
  // variável aponta para nil ($0000:0000)
  List := nil;
  List.Add('e finalmente');
end;

Essas são receitas certas pra cometer violações de acesso de leitura de memória. Normalmente, em blocos de código pequenos esse tipo de coisa não ocorre, porque temos mais visibilidade e controle sobre o tempo de vida das variáveis. O problema fica maior quando o objeto é um parâmetro recebido de outra parte qualquer do código. Quem garante que o valor passado como parâmetro é válido?

Só pra ficar claro:

procedure ShowText(List: TStringList);
begin
  ShowMessage(List.Text); // AV? List é válido?
end;
 
procedure TestAV;
var
  Strings: TStringList;
begin
  ShowText(Strings);
end;

Aqui TestAV chama ShowText passando como parâmetro um valor de objeto inválido, porque nunca foi alocado. Seja o valor igual a nil ou uma valor aleatório qualquer (dependendo do contexto, a variável pode conter “lixo” de memória), quando ShowText usar a propriedade Text do objeto vai produzir um AV.

O que eu uso para prevenir esses casos é um Assert básico.

O código acima com Assert ficaria assim:

procedure ShowText(List: TStringList);
begin
  // Exceção (EAssert) caso List seja inválido
  Assert(List <> nil);
 
  // A partir daqui é seguro usar List
  ShowMessage(List.Text);
end;

Assert tem como parâmetro um valor condicional (Boolean). Se falhar (False), dispara uma exceção EAssert. A vantagem é que, ao contrário de uma exceção de violação de acesso, a IDE vai te mostrar certinho onde ocorreu a violação, isto é, vai parar o debugger naquela linha do Assert. Sem uma verificação desse tipo, seria muito mais difícil identificar em todo o seu projeto o ponto que deu problema.

Se você quiser, pode ainda usar usar o segundo parâmetro opcional de Assert para dar uma mensagem ao usuário. Por exemplo: “Assert(List <> nil, ‘A função ShowText não recebeu um objeto TStringList válido’)”.

Outra vantagem do Assert é que depois que você estiver confiante da correção do código (quando esse dia chegar :) ), você poderá desativar um checkbox nas opções de compilação do Delphi para
simplesmente omitir todo o código de verificação de “Assert” globalmente no projeto (lembre-se de dar um Build depois que mudar essa opção).

Dá muito trabalho? “Better safe, than sorry.”

Que atire a primeira pedra quem nunca fez um “Access Violation”. ;-)

Comments

Deixe uma Resposta




XHTML: Você pode usar essas tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="">