Só para adicionar um pouco mais de profundidade: o deslocamento depende do tamanho do tipo elemento que está sendo armazenado.
Seguindo seu exemplo de números e supondo inteiros com sinal de 32 bits, o deslocamento pode ser visualizado como:
*(&arr[0] + sizeof(int32_t) * index)
O código acima é a mesma coisa que:
arr[index]
No primeiro código, arr
é o nosso array e &arr[0]
é o endereço de memória do primeiro elemento de arr
. sizeof
é um operador (e não uma função) que retorna o tamanho do tipo do que foi passado como parâmetro, em bytes. Ao final de tudo, é pegado o valor efetivo dentro do endereço de memória adicionando *(...)
em volta de tudo isso.
Note que, apesar de estar usando sintaxe de C para ilustração, a execução não funciona da maneira que você espera, pois o certo para chegar no índice desejado é *(&arr[0] + index)
, e o compilador faria o trabalho de descobrir o tamanho do tipo dos elementos armazenados em arr
. E, claro, não tem porquê fazer isso, é só usar o segundo código.
Logo, se esse array descrito começa no endereço 0x1000, ele tem seu elemento de índice 1 sendo armazenado em:
0x1000 + sizeof(int32_t) * 1
0x1000 + 4 * 1
0x1004
Vale salientar que nem todas as linguagens usam o 0 como índice base. Lua, por exemplo, é amplamente usada no mundo dos jogos e utiliza base 1.