Access Violation

sexta-feira, janeiro 20, 2006

O que esta errado com este código (parte II) : Resposta
Se você executar o programa em questão verá que o resultado impresso sera:

N: 1 J: 7
N: 2 J: 6
N: 3 J: 5
N: 4 J: 4

Certamente esse não era o resultado esperado!

Olhando mais a fundo você observará que foram impressos itens consecutivos do vetor, no entanto a linha 38 (função printIt()) avança o índice de dois em dois (ou seja, os itens deveriam ser impressos intercalados)!

Para entender melhor o problema vamos analiasar a função "printIt()" (muito simples por sinal).

Na linha 38 temos um loop de 0 até o n. A cada passo o índice (i) utilizado para acessar o array é incrementado de dois, ou seja, o primeiro elemento acessado é o de índice 0 (zero), seguido pelos elementos de índice 2, 4 e assim sucessivamente.

Este acesso se dá através da chamada do método print() do objeto que se encontra no índice corrente (0, 2, 4, etc) na linha 40:

a[i].print();

O problema esta na forma como o compilador gera código para resolver a expressão a[i]. Esta expressão é equivalente a *(a + i) que nada mais é que aritmética de ponteiros. Bom, a especificação da linguagem determina que esta aritmética deve ser realizada baseada no tamanho do tipo em questão (neste caso o tipo da variável a), ou seja, sendo p um ponteiro igual a 1000, o endereço (p+2) é calculado como 1000 + 2 * sizeof(*p).

Se p for um ponteiro para char então p+2 será efetivamente:


1000 + 2 * sizeof(char) => 1000 + 2 * 1 == 1002


Contudo se p for um ponteiro para int então p+2 sera:


1000 + 2 * sizeof(int) => 1000 + 2 * 4 => 1008 (isto em 32 bits).


Veja o exemplo abaixo:

int v[4];
int *p = v; // p == &v[0];
int *p1 = p + 1; // p1 == &v[1];

char *pc = (char*) p;
char *p1c = (char*) p1;

printf("Diferença: %d", p1 - p);

Agora que você já relembrou como é realizada aritimética de ponteiros, voltemos à nossa expressão:

a[i].print();

Como dissemos esta expressão é equivalente à:

base *p = &a[i];
p->print();

que é equivalente à:

base *p = (a + i);
p->print();

Já percebeu? Não?

Imagine que o vetor a[] possua a seguinte configuração na memória:

a[0] (1000)
a[1] (1016)
a[2] (1032)
... ...
a[n]

No diagrama acima observamos que o primeiro elemento de a[], ou seja, a[0] se encontra no endereço 1000. E a[1]? qual seu endereço?

Baseado no diagrama acima responderíamos: 1016 é claro!

O problema é que o compildor não viu este diagrama; então para calcular este endereço o mesmo irá empregar aritmética de ponteiros com as informações que possui em mãos, ou seja, o endereço inicial de a e seu tipo um vetor
de objetos do tipo base
:

&a[i] == (a + i * sizeof(base))

Observe bem a expressão acima!

O compilador esta considerando o tamanho de "base" para calcular o deslocamento do elemento de indice "i" a partir do início do vetor, quando na realidade temos um vetor de objetos "derived", ou seja, para funcionar deveríamos tomar sizeof(derived) e não sizeof(base).

Assim, &a[1] será (assumindo que o primeiro elemento do vetor "a" se encontra no endereço 1000):

&a[1] => (1000 + 1 * sizeof(base)) => (1000 + 1 * 8) => 1008


Opss!! Não deveria ser 1016?

Então para a função "printIt()" o vetor 'a' tem a seguinte configuração:

Visão printIt()
---------------
a[0] (1000)
a[1] (1008)
a[2] (1016)
a[3] (1024)
a[4] (1032)
a[5] (1040)
a[6] (1048)
... ...
a[n]

Observe que isto ocorre porque sizeof(base) * 2 == sizeof(derived). Isto não é uma coicidência! Eu escrevi as classes para obter este resultado.

Na vida real, um erro como este (utilizar um vetor de objetos de forma polimorfica) normalmente causaria exceções de acesso a memória (access violations).

Quer fazer um teste? É bem simples, basta modificar o código de forma que o índice 'i' seja incrementado de 1 (um) ao invés de 2 (dois). Isto fará com que o programa tente acessar &a[1] que na realidade não é o endereço do segundo elemento do vetor mas sim um endereço no meio do primeiro objeto do vetor.

Como resolver isto? Simples! Nunca utilize vetores de objetos de forma polimorfica! Utilize um vetor de ponteiros para objetos!

Espero ter contribuido!

Adriano

[+/-] mostrar/esconder este post