Wenn wir in C ein Array wie a[10]
haben, haben a
und &a
denselben Zeigerwert (aber nicht den gleichen Typ). Ich möchte wissen, warum C so entworfen wurde?
Soll der zusätzliche Speicherplatz für das Speichern von &a
gespart werden? ... Dies ist sinnvoll, wenn Sie daran denken, dass a
niemals auf einen anderen Ort zeigen kann, sodass das Speichern von &a
bedeutungslos ist.
die Tatsache, dass
a
niemals auf einen anderen Ort zeigen kann
Dies ist jedoch keine Tatsache. Wenn a
ein Array ist, zeigt a
nirgends, weil a
kein Zeiger ist. Gegeben int a[42];
, a
benennt ein Array von 42 int
Objekten; Es ist kein Zeiger auf ein Array von 42 int
-Objekten (das wäre int (*a)[42];
).
&x
gibt Ihnen die Adresse des Objekts x
; Wenn x
eine Array-Typ-Variable ist, gibt &x
Ihnen die Adresse des Arrays; wenn nichts anderes, ist dies konsistent mit dem Verhalten von &
für jedes andere Objekt.
Eine bessere Frage wäre: "Warum zerfällt ein Array (wie a
) in den meisten Fällen zu einem Zeiger auf sein Anfangselement, wenn es verwendet wird?" Obwohl ich nicht sicher weiß, warum die Sprache auf diese Weise entworfen wurde, macht sie die Spezifikation vieler Dinge viel einfacher, insbesondere ist Arithmetik mit einem Array im Grunde die gleiche wie Arithmetik mit einem Zeiger.
Das Design ist ziemlich elegant und ziemlich notwendig, wenn man bedenkt, wie sich ein Array auf Assembly-Ebene verhält. Berücksichtigen Sie bei der x86-Assembly den folgenden C-Code:
%Vor% Das Array a
belegt 20 Byte auf dem Stack, da ein Int auf den meisten Plattformen normalerweise 4 Byte belegt. Wenn das Register EBP
auf die Basis des Aktivierungsdatensatzes des Stapels zeigt, würden Sie sich die folgende Assembly für die obige main()
-Funktion ansehen:
Der Assembly-Befehl LEA
oder "Load Effective Address" berechnet die Adresse aus dem Ausdruck seines zweiten Operanden und verschiebt ihn in das vom ersten Operanden angegebene Register. Jedes Mal, wenn wir diesen Befehl aufrufen, ist es wie das C-Äquivalent des Adressenoperators. Sie werden feststellen, dass die Adresse, an der das Array startet (dh [ebp - 20]
oder 20 Bytes, subtrahiert von der Basis der Stack-Pointer-Adresse, die sich im Reigister EBP
befindet), immer an jede der Funktionen% co_de übergeben wird % und f
. Das ist ziemlich viel - nur so kann es auf Maschinencode-Ebene gemacht werden, um auf einen Teil des Speichers zu verweisen, der im Stapel einer Funktion einer anderen Funktion zugeordnet ist, ohne den Inhalt des Arrays tatsächlich kopieren zu müssen.
Der Take-away ist, dass Arrays nicht gleich sind wie Zeiger, aber gleichzeitig die einzige effektive Möglichkeit, auf ein Array auf der rechten Seite des Zuweisungsoperators zu verweisen, oder indem man es an eine Funktion weiterleitet, wird es als Referenz übergeben, was bedeutet, dass die Bezugnahme auf das Array by-name tatsächlich auf der Maschinenebene genau das Gleiche ist wie das Abrufen eines Zeigers auf das Array. Daher gehen g
, a
und sogar &a
in diesen Situationen auf der Ebene des Maschinencodes in die gleiche Menge von Anweisungen über (in diesem Beispiel Fall &a[0]
.) Wiederum ist ein Array-Typ nicht ein Zeiger, und lea eax, [ebp - 20]
, und a
sind nicht vom selben Typ, aber da es einen Teil des Speichers bezeichnet, ist der einfachste und effektivste Weg, einen Verweis darauf zu bekommen, ein Zeiger.
Tatsächlich ist a[0]
tatsächlich der gleiche Speicherort wie a
. &a
stellt die Adresse dar, in der a
gespeichert ist.
Es gibt verschiedene Möglichkeiten, die gleiche Notation darzustellen.
Zum Index 3 des Arrays ( a[2]
) zu gehen, ist dasselbe wie bei a + sizeof( typeof(a) ) * 3
, wobei typeof(a)
der Typ der Variablen ist.
Ihre Erklärung ist auf dem richtigen Weg, obwohl ich bezweifle, ob der Betrag des Speicherplatzes das Problem war, sondern eher der Spezialfall, dass es überhaupt zugewiesen werden muss. Normalerweise hat jedes Objekt, mit dem C arbeitet, einen Wert (oder Werte) und eine Adresse. Ein tatsächlich zugeordneter Zeiger hat also bereits eine Adresse und es ist sinnvoll, für echte Zeiger sowohl Wert als auch Adresse zur Verfügung zu haben.
Aber eine Array-Referenz ist bereits eine Adresse. Damit C einen doppelt indirekten Zeiger über die & amp; Der Operator hätte die Zuweisung von Speicherplatz an anderer Stelle benötigt, und dies hätte eine große Divergenz in der Philosophie für den einfachen frühen dmr-C-Compiler dargestellt.
Wo es diesen neuen Zeiger gespeichert hätte, ist eine gute Frage. Mit der gleichen Speicherklasse wie das Array? Was wenn es ein Parameter wäre? Es ist Büchse der Pandora und der einfachste Weg, es zu lösen, besteht darin, die Operation zu definieren. Wenn der Entwickler einen indirekten Zeiger haben möchte, kann er immer einen deklarieren.
Außerdem ist es sinnvoll, dass &
die Adresse eines Array-Objekts zurückgibt, da dies mit seiner Verwendung an anderer Stelle konsistent ist.
Eine gute Möglichkeit, dies zu betrachten, ist zu sehen, dass Objekte Werte und Adressen haben und die Array-Referenz nur eine Kurzschriftsyntax ist. Eigentlich erfordert &a
wäre ein bisschen pedantisch gewesen, weil die Referenz a
ohnehin keine andere Interpretation gehabt hätte.
B ist ein direkter Vorfahre von C. Es war eine untypisierte Sprache, in der die Syntax
ist %Vor%hatte mehr oder weniger die Bedeutung von
%Vor%in C. I.E. Es reserviert 10 Wörter Speicher und initialisiert die Variable mit der Adresse der Speicherzone.
Als C entwickelt wurde, wurde es als nützlich angesehen, die Tatsache beizubehalten, dass ein Array (BTW nicht nur eine Arrayvariable, irgendein Arraywert, kann man es mit Zeigern und mehrdimensionalen Arrays sehen) in einem Zeiger auf sein erstes Element zerfällt .
B Handbuch in Dennis Ritchies Homepage , die andere historische Informationen über C und Unix enthält.