La spiegazione è un pò complicata e va a toccare come gestisce il compilatore le varibili gli indirizzi ecc... ma cercherò di essere chiaro.

Quando tu dichiari

char a[5];

il compilatore riserva 5 byte nella ram. Ora tu ti aspetteresti che l'indirizzo del primo byte vada a finire in un'altra cella di memoria (il cosidetto puntatore ad 'a'). In realtà non è cosi.
Il compilatore si ricorda qual'è l'indirizzo di &a[0] ed ogni volta che incontra ad es.

a[2] = 'd';

lui somma l'indirizzo che ha in memoria (&a[0]) con 2 (l'offset) e crea il codice asm che il computer dovrà eseguire

codice:
In molto pseudo ASM

metti &a[2] in 0x0c
metti 'd' nell'accumulatore
mouvi il contenuto dell'accumulatore all'indirizzo *puntato* da 0x0c
Come vedi viene creato un puntatore (all'indirizzo 0x0c) ma che muore subito dopo.

Stessa cosa (ma non te la sto a scrivere in preudo asm perchè non ho voglia :P) se hai

while(i){
a[i]= i;
i--;
}

In pratica quando lui vede la variabile a sostituisce ad essa il suo indirizzo per poi sommarlo all'eventuale offset.
Quindi se a diventa (ad es.) 0x0A

fare &a vuol dire fare &0x0A e l'indirizzo di 0x0A è 0x0A!