Warum fangen Arrays bei 0 an?

Das Indizieren von Array-Elementen beginnend beim Index 0 bildet die interne Darstellung des Arrays im Speichers ab. Intern wird ein Array durch die Adresse a, an der der Speicherbereich für die Array-Daten beginnt, repräsentiert.

|<-- k -->|<-- k -->|<-- k -->|
+---------+---------+---------+------------
|   a[0]  |   a[1]  |   a[2]  |
+---------+---------+---------+------------
|         |         |         |
a       a+k*1     a+k*2

Für das Speichern eines Elements des Arrays werden k Bytes benötigt. Addiert man zur Adresse a, den Wert 0*k, so erhält man die Adresse des 1-ten Array-Elements. Addiert man zur Adresse a, den Wert 1*k, so erhält man die Adresse des 2-ten Array-Elements. …
Allgemein: Addiert man zur Adresse a, den Wert n*k, so erhält man die Adresse des n+1-ten Array-Elements.
Mit n*k berechnet man also die Position des Array-Elements mit dem Index n im Speicher relativ zu a.

In C kann mit Adressen (pointer) gerechnet werden. Dabei wird der oben beschriebene Faktor k für die Größe eines Array-Elements implizit eingesetzt.
Beispiel:

int a[10];
int b = *a;
int c = *(a + 2);
  1. a ist ein Zeiger (Adresse) auf den Beginn des Arrays.
  2. *a oder *(a + 0) ist der Inhalt des ersten Array-Elements – Kurzschreibweise: a[0].
  3. *(a + 2) ist der Inhalt des Array-Elements mit Index 2 – Kurzschreibweise: a[2].