Piénsalo de esta forma, de hecho es así como funciona:
Un array es un conjunto de elementos del mismo tipo puestos uno detrás de otro en la memoria.
El identificador de un array, su nombre, te devuelve su posición de memoria de inicio (apunta al primero elemento). Supongamos que hemos llamado a un array mi_array:
Siendo que mi_array tiene una dirección de inicio, supongamos, que es la 100 entonces el primer elemento empieza en 100. Siendo así 100+0 es la dirección del primer elemento o, en notación de arrays, mi_array[0] es la dirección del primer elemento.
Si nos vamos a la siguiente posición de memoria encontraremos el segundo elemento: 100+1 será el segundo elemento, lo que en notación de arrays sería mi_array[1].
Ahora supongamos que el array es de tres elementos. Hasta ahora hemos accedido a los dos primeros. Para acceder al tercer elemento, y siguiendo con el planteamiento anterior, se encontraría en la posición 100+2, o lo que es lo mismo en notación de arrays mi_array[2].
Y es por esto que los arrays empiezan con el índice 0 y terminan con un índice una unidad menos que su número de elementos.