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.