Ein Array-Name ist selbst kein Zeiger, sondern zerfällt in den meisten Kontexten in einen Zeiger auf das erste Element des Arrays. Es ist so, weil die Sprache es so definiert.
Ab C11 6.3.2.1 L-Werte, Arrays und Funktionsbezeichner , Absatz 3:
Wenn es sich nicht um den Operanden des Operators
sizeof
, den Operator_Alignof
oder den unären Operator&
handelt , oder ist ein String-Literal, das zum Initialisieren eines Arrays verwendet wird, wird ein Ausdruck vom Typ "Array of type " in einen Ausdruck mit dem Typ "pointer to type " konvertiert auf das Anfangselement des Array-Objekts und ist kein Lvalue.
Sie können mehr über dieses Thema (und vieles über das subtile Verhalten) aus dem Abschnitt Arrays und Zeiger des FAQ von comp.lang.c .
Editorial beiseite: Die gleiche Art von Verhalten findet in C ++ statt, obwohl die Sprache es ein bisschen anders spezifiziert. Als Referenz, von einem C ++ 11-Entwurf habe ich hier, 4.2 Array-to-Pointer-Konvertierung , Absatz 1:
Ein lvalue oder rvalue vom Typ "Array von
N
T
" oder "Array von unbekannter Grenze vonT
" kann in einen rvalue vom Typ "pointer toT
" konvertiert werden. Das Ergebnis ist ein Zeiger auf das erste Element des Arrays.
Der historische Grund für dieses Verhalten kann hier gefunden werden.
C wurde aus einer früheren Sprache namens B (go figure) abgeleitet. B war eine typlose Sprache, und Speicher wurde als eine lineare Anordnung von "Zellen" behandelt, im Grunde vorzeichenlose Ganzzahlen.
In B, wenn Sie ein N-Element-Array wie in
deklariert haben %Vor% N Zellen wurden für das Array reserviert, und eine andere Zelle wurde reserviert, um die Adresse des ersten Elements zu speichern, das an die Variable a
gebunden war. Wie in C wurde die Array-Indizierung durch Zeigerarithmetik durchgeführt:
Dies funktionierte ziemlich gut, bis Ritchie begann, Strukturtypen zu C hinzuzufügen. Das Beispiel, das er in der Arbeit gibt, ist ein hypothetischer Dateisystemeintrag, der eine Knoten-ID gefolgt von einem Namen ist:
%Vor%Er wollte, dass der Inhalt des Strukturtyps mit den Daten auf dem Datenträger übereinstimmt. 2 Bytes für eine ganze Zahl, unmittelbar gefolgt von 14 Bytes für den Namen. Es gab keinen guten Ort, um den Zeiger auf das erste Element des Arrays zu speichern.
Also hat er es losgeworden. Anstatt den Speicher für den Zeiger zu reservieren, entwarf er die Sprache so, dass der Zeigerwert aus dem Array-Ausdruck selbst berechnet wurde.
Dies ist übrigens der Grund, warum ein Array-Ausdruck nicht das Ziel einer Zuweisung sein kann; Es ist praktisch das gleiche wie das Schreiben von 3 = 4;
- Sie würden versuchen, einem anderen Wert einen Wert zuzuweisen.
Carl Norum hat der Sprachanwältin eine Antwort auf die Frage gegeben (und ich habe meine Meinung dazu), hier kommt die ausführliche Antwort auf die Implementierung:
Für den Computer ist jedes Objekt im Speicher nur ein Bereich von Bytes und, was die Speicherbehandlung betrifft, eindeutig durch eine Adresse für das erste Byte und eine Größe in Bytes identifiziert. Selbst wenn Sie ein int
im Speicher haben, ist seine Adresse nicht mehr oder weniger als die Adresse seines ersten Bytes. Die Größe ist fast immer implizit: Wenn Sie einen Zeiger auf ein int
übergeben, weiß der Compiler seine Größe, weil er weiß, dass die Bytes an dieser Adresse als int
interpretiert werden sollen. Dasselbe gilt für Strukturen: Ihre Adresse ist die Adresse ihres ersten Bytes, ihre Größe ist implizit.
Nun könnten die Sprachdesigner eine ähnliche Semantik mit Arrays wie mit Strukturen implementiert haben, aber aus einem guten Grund: Das Kopieren war dann noch ineffizienter als jetzt, verglichen mit einem Zeiger, Strukturen waren schon vorhanden herumgereicht werden, wobei Zeiger die meiste Zeit verwendet werden, und Arrays sollen normalerweise groß sein. Prohibitiv groß, um Wert Semantik für sie durch Sprache zu erzwingen.
Somit wurden Arrays immer gezwungen, Speicherobjekte zu sein, indem sie festlegten, dass der Name eines Arrays virtuell einem Zeiger entsprechen würde. Um die Ähnlichkeit von Arrays nicht mit anderen Speicherobjekten zu brechen, wurde wiederum gesagt, dass die Größe implizit ist (zur Sprachimplementierung, nicht zum Programmierer!): Der Compiler könnte die Größe eines Arrays einfach vergessen, wenn es übergeben wurde irgendwo anders und verlassen sich auf den Programmierer zu wissen, wie viele Objekte innerhalb des Arrays waren.
Dies hatte den Vorteil, dass Array-Zugriffe äußerst einfach sind; sie zerfallen in eine Art Zeigerarithmetik, indem sie den Index mit der Größe des Objekts im Array multiplizieren und diesen Offset zum Zeiger hinzufügen. Das ist der Grund, warum a[5]
genau dasselbe ist wie 5[a]
, es ist eine Abkürzung für *(a + 5)
.
Ein weiterer performancebezogener Aspekt ist, dass es extrem einfach ist, aus einem Array ein Subarray zu machen: nur die Startadresse muss berechnet werden. Es gibt nichts, was uns zwingen würde, die Daten in ein neues Array zu kopieren, wir müssen nur daran denken, die richtige Größe zu verwenden ...
Also, ja, es hat grundlegende Gründe in Bezug auf Einfachheit und Leistung der Implementierung, dass Array-Namen zu Zeigern verfallen, wie sie es tun, und wir sollten uns darüber freuen.