Access Violation

quinta-feira, fevereiro 09, 2006

O que esta errado com este programa (parte iv) : Resposta

O problema com o programa do post anterior se encontra na linha 8 da função fatorial (isto não é muito difícil de se perceber).
Esta linha deveria se parecer com:
 0: return n * fatorial(n - 1);

ou seja, o desenvolvedor se esqueceu de incluir o return. É claro que o compilador gerou uma warning (aviso) mas eu a ignorei conscientemente.








Atenção

Ignorar avisos (warnings) do compilador não é uma prática saudável e eu não recomendo a ninguém fazê-lo! Pelo contrário; analise todas
as warnings geradas em seu código e entenda o porque o compilador esta reclamando. Não assuma que "o compilador possui bugs" muito menos
tome a postura arrogante de achar que sabe mais que o compilador. Se depois de estudar o motivo da warning você concluir que o código que causou
a warning é legítimo, desabilite a warning específica (via #pragma warning) no trecho de código em questão.


Agora vem a segunda pergunta: Porque, mesmo faltando o return (ou seja, o retorno da função não foi especificado nesta condição) o programa gera o resultado
correto (pelo menos compilando com o BC++ 3.0 e o VC7.1)?

Para responder esta pergunta teremos que recorrer à linguagem assembly do programa (não se preocupe, não é tão complicado assim).

Created with colorer-take5 library. Type 'asm'

0: fatorial:
1: push ebp
2: mov ebp,esp
3: cmp dword ptr [ebp+0x8],0x1
4: jnz proximo_valor
5: mov eax,0x1
6: jmp fim
7:
8: proximo_valor:
9: mov eax,[ebp+0x8]
10: sub eax,0x1
11: push eax
12: call fatorial
13: add esp,0x4
14: imul eax,[ebp+0x8]
15: mov [ebp+0x8],eax
16:
17: fim:
18: pop ebp
19: ret
20:
21: ; Programa principal (apenas a parte da chamada da função
22: ; fatorial e do retorno da mesma.)
23:
24: main:
25: ; [ebp - 0x4] ==> Variável I
26: ; [ebp - 0x8] ==> Variável N
27: call fatorial
28: add esp,0x4
29: mov [ebp-0x4],eax ; Aqui esta o motivo!
30: mov ecx,[ebp-0x4]
31: push ecx ; Efetivamente colocou o valor de EAX na pilha para a função printf!
32: mov edx,[ebp-0x8]
33: push edx
34: push 0x40cb40
35: call printf
36: add esp,0xc
37: mov esp,ebp
38: pop ebp
39: ret


O código da função fatorial começa na linha 0 (zero) e vai até a linha 19 (instrução ret). O código do programa principal começa na linha 24
e se extende até a linha 39.

Na linha 25 temos a chamada à função fatorial a partir do programa principal (observe que não inclui todo o código assembly do programa principal
por motivos de simplicidade).

Vamos analisar o código da função fatorial e, quando pertinente, entrar no código do programa principal.

Nas linhas 1 e 2 temos o código de prologo padrão de uma chamada de função em c; na linha 3 temos a comparação
da variável n com 1; se forem iguais, a instrução jnz na linha 4 não irá desviar o programa para o ponto proximo_valor, ou seja,
a execução do programa irá para a linha 5 que atribui 1 ao registrador eax e retorna para o programa principal.

Caso n seja maior que 1 a instrução jnz na linha 4 desviará a execução do programa para o label proximo_valor na linha 8; na linha 9
o valor valor de n é atribuido a eax, subtraido de 1 na linha 10, colocado na pilha novamente na linha 11 e, finalmente, na linha 12 a função
fatorial é executada recursivamente.

O ponto importante aqui é observarmos que o valor do registrador eax é utilizado como retorno da função fatorial. Podemos observar isto tanto na linha 5
da função, quanto na linhas 29, 30 e 31 do programa principal (visto que ele passa o valor de eax para a posição de memória [ebp-0x4], ou seja, a variável i, o que deixa claro que eax contém
o valor de retorno da função).

Assim, compreendemos que o programa funciona apenas quando o compilador mantém o resultado da expressão:

Created with colorer-take5 library. Type 'c'

0: n * fatorial(n - 1);


no registrador eax como é o caso quando compilamos com o BC++ 3.0 ou VC 7.1 (observe as linhas 15 e 16 do programa assembly):

Created with colorer-take5 library. Type 'asm'

0: imul eax,[ebp+0x8]
1: mov [ebp+0x8], eax


que armazena o resultado da referida expressão no registrador eax.

Por outro lado observe o código assembly gerado pelo VC 6.0 (usando a linha cl fatorial.c /Fafatorial.vc6.asm e editado para simplificar o código):

Created with colorer-take5 library. Type 'asm'

0: fatorial:
1: push ebp
2: mov ebp, esp
3: cmp DWORD PTR _n$[ebp], 1
4: jne proximo_valor
5:
6: mov eax, 1
7: jmp fim
8:
9: proximo_valor:
10: mov eax, DWORD PTR _n$[ebp]
11: sub eax, 1
12: push eax
13: call _fatorial
14: add esp, 4
15: mov ecx, DWORD PTR _n$[ebp]
16: imul ecx, eax
17: mov DWORD PTR _n$[ebp], ecx
18:
19: fim:
20: pop ebp
21: ret 0
22:
23: main:
24: mov eax, DWORD PTR _n$[ebp]
25: push eax
26: call _fatorial
27: add esp, 4
28: mov DWORD PTR _i$[ebp], eax
29: mov ecx, DWORD PTR _i$[ebp]
30: push ecx
31: mov edx, DWORD PTR _n$[ebp]
32: push edx
33: push OFFSET FLAT:$SG480
34: call _printf
35: add esp, 12
36: mov esp, ebp
37: pop ebp
38: ret 0


Observe que nas linhas 16 e 17 o resultado da expressão

Created with colorer-take5 library. Type 'c'

0: n * fatorial(n - 1);

é armazenado no registrador ecx ao invés do eax. Por isso quando compilado no vc6 este programa não funciona!

Observe que mesmo com os compiladores onde observei que o programa funcionava, qualquer mudança de opção (por exemplo otimização do código gerado) pode fazer com que o programa resultante não funcione.

[+/-] mostrar/esconder este post