Sind a, & a, * a, a [0], & a [0] und & a [0] [0] identische Zeiger?

8

Ich habe das folgende C-Programm:

%Vor%

Es gibt die folgende Ausgabe:

%Vor%

Ich kann nicht verstehen, warum &a , a , *a - alle identisch sind. Dasselbe gilt für a[0] , &a[0] und &a[0][0] .

BEARBEITEN:

Dank der Antworten habe ich den Grund verstanden, warum diese Werte sich als gleich herausstellen. Diese Zeile aus dem Buch von Kernighan & amp; Ritchie erwies sich als der Schlüssel zu meiner Frage:

%Vor%

Also, dadurch bekommen wir

a = &a[0] und

a[0] = &a[0][0] (unter Berücksichtigung von a als Array von Arrays)

Intuitiv ist jetzt der Grund hinter der Ausgabe klar. Aber wenn man bedenkt, wie Zeiger in C implementiert sind, kann ich nicht verstehen, wie a und &a gleich sind. Ich nehme an, dass es eine Variable a im Speicher gibt, die auf das Array zeigt (und die Startadresse dieses Array-Speicherblocks wäre der Wert dieser Variablen a ).

Aber wenn wir &a machen, heißt das nicht, die Adresse des Speicherplatzes zu nehmen, an dem die Variable a gespeichert wurde? Warum sind diese Werte gleich?

    
Koderok 21.08.2013, 15:08
quelle

8 Antworten

13

Sie sind keine identischen Zeiger. Sie sind Zeiger verschiedener Typen, die alle auf denselben Speicherort verweisen. Gleicher Wert (Sorte), verschiedene Typen.

Ein zweidimensionales Array in C ist nicht mehr oder weniger als ein Array von Arrays.

Das Objekt a hat den Typ int[2][2] oder ein 2-Element-Array aus einem 2-Element-Array von int .

Jeder Ausdruck des Array-Typs wird in den meisten, aber nicht allen Kontexten implizit in einen Zeiger auf das erste Element des Array-Objekts umgewandelt ("verfällt"). Daher ist der Ausdruck a , es sei denn, es ist der Operand von unary & oder sizeof , vom Typ int(*)[2] und entspricht &a[0] (oder &(a[0]) , wenn das klarer ist ). Es wird ein Zeiger auf Zeile 0 des 2-dimensionalen Arrays. Es ist wichtig zu beachten, dass dies ein Zeiger Wert (oder äquivalent eine Adresse ) ist, kein Zeiger Objekt ; Es gibt hier kein Zeigerobjekt, es sei denn, Sie erstellen explizit eines.

Betrachten Sie also die verschiedenen Ausdrücke, nach denen Sie gefragt haben:

  • &a ist die Adresse des gesamten Array-Objekts; Es ist ein Zeigerausdruck vom Typ int(*)[2][2] .
  • a ist der Name des Arrays. Wie oben diskutiert, "zerfällt" es zu einem Zeiger auf das erste Element (Zeile) des Array-Objekts. Es ist ein Zeigerausdruck vom Typ int(*)[2] .
  • *a Dereferenzierung der Zeigerausdruck a . Da a (nachdem es abklingt) ist ein Zeiger auf ein Array von 2 int s, *a ist ein Array von 2 int s. Da dies ein Array-Typ ist, verfällt er (in den meisten, aber nicht allen Kontexten) in einen Zeiger auf das erste Element des Array-Objekts. Es ist also vom Typ int* . *a entspricht &a[0][0] .
  • &a[0] ist die Adresse der ersten (0.) Zeile des Array-Objekts. Es ist vom Typ int(*)[2] . a[0] ist ein Array-Objekt; Es zerfällt nicht in einen Zeiger, weil es der direkte Operand von unary & ist.
  • &a[0][0] ist die Adresse des Elements 0 der Zeile 0 des Array-Objekts. Es ist vom Typ int* .

Alle diese Zeigerausdrücke beziehen sich auf den gleichen Ort im Speicher. Diese Position ist der Anfang des Array-Objekts a ; es ist auch der Anfang des Array-Objekts a[0] und des int -Objekts a[0][0] .

Der richtige Weg, um einen Zeigerwert zu drucken, ist die Verwendung des "%p" Formats und , um den Zeigerwert in void* :

zu konvertieren %Vor%

Diese Umwandlung in void* ergibt eine "rohe" Adresse, die nur einen Speicherort im Speicher angibt, nicht den Typ des Objekts an diesem Ort. Wenn Sie also mehrere Zeiger verschiedener Typen haben, die auf Objekte zeigen, die am selben Speicherort beginnen, ergibt das Konvertieren aller in void* den gleichen Wert.

(Ich habe die inneren Funktionen des [] Indizierungsoperators beschrieben. Der Ausdruck x[y] entspricht per Definition der *(x+y) , wobei x ein Zeiger ist (möglicherweise das Ergebnis der impliziten Konvertierung von ein Array) und y ist eine ganze Zahl oder umgekehrt, aber das ist hässlich, arr[0] und 0[arr] sind äquivalent, aber das ist nur nützlich, wenn Sie bewusst verschleierten Code schreiben. Wenn wir diese Äquivalenz berücksichtigen, dann nimmt einen Absatz oder so zu beschreiben, was a[0][0] bedeutet, und diese Antwort ist wahrscheinlich schon zu lang.)

Der Vollständigkeit halber werden die drei Kontexte, in denen ein Ausdruck des Array-Typs nicht ist, implizit in einen Zeiger auf das erste Element des Arrays konvertiert:

  • Wenn es der Operand von unary & ist, ergibt &arr die Adresse des gesamten Array-Objekts;
  • Wenn es der Operand von sizeof ist, ergibt sizeof arr die Größe in Bytes des Array-Objekts, nicht die Größe eines Zeigers; und
  • Wenn es ein String-Literal in einem Initialisierer ist, der zum Initialisieren eines Array- (Unter-) Objekts verwendet wird, so kopiert char s[6] = "hello"; den Array-Wert in s , anstatt ein Array-Objekt mit einem Zeigerwert unsinnig zu initialisieren. Diese letzte Ausnahme gilt nicht für den Code, nach dem Sie fragen.

(Der N1570 Entwurf des ISO C-Standards 2011 falsch gibt an, dass _Alignof eine vierte Ausnahme ist, das ist falsch, da _Alignof nur auf einen Namen in Klammern angewendet werden kann, nicht auf einen Ausdruck. Der Fehler wird im endgültigen C11-Standard korrigiert.)

Empfohlene Lektüre: Abschnitt 6 der comp.lang.c FAQ .

    
Keith Thompson 21.08.2013, 18:04
quelle
6

Da alle Ausdrücke auf den Anfang des Arrays zeigen:

%Vor%

a zeigt auf das Array, nur weil es ein Array ist, also a == &a[0]

und &a[0][0] befinden sich in der ersten Zelle des 2D-Arrays.

    
Tomer W 21.08.2013 15:11
quelle
5

Es werden die gleichen Werte ausgegeben, da alle auf denselben Ort zeigen.

Nachdem das gesagt wurde,

&a[i][i] hat den Typ int * , was ein Zeiger auf eine Ganzzahl ist.

a und &a[0] haben den Typ int(*)[2] , der auf ein Array von 2 ints verweist.

&a hat den Typ int(*)[2][2] , der einen Zeiger auf ein 2-D array anzeigt, oder einen Zeiger auf ein Array aus zwei Elementen, in dem jedes Element ein Array aus 2-Ints ist.

Sie sind also alle von unterschiedlichem Typ und verhalten sich anders, wenn Sie mit der Zeigerarithmetik beginnen.

(&a[0][1] + 1) zeigt auf das nächste ganzzahlige Element im 2-D-Array, d. h. auf a[0][1]

&a[0] + 1 zeigt auf das nächste Array von ganzen Zahlen, d. h. auf a[1][0]

&a + 1 zeigt auf das nächste 2-D-Array, das in diesem Fall nicht existiert, wäre aber a[2][0] , falls vorhanden.

    
Uchia Itachi 21.08.2013 15:30
quelle
4
%Vor%     
Lidong Guo 21.08.2013 15:15
quelle
3

Sie wissen, dass a die Adresse des ersten Elements Ihres Arrays ist und a[X] entsprechend dem C-Standard gleich *(a + X) ist.

Also:

&a[0] == a , weil &a[0] dasselbe ist wie &(*(a + 0)) = &(*a) = a .

&a[0][0] == a , weil &a[0][0] dasselbe ist wie &(*(*(a + 0) + 0))) = &(*a) = a

    
nouney 21.08.2013 15:14
quelle
3

Ein 2D-Array in C wird wie ein 1D-Array behandelt, dessen Elemente 1D-Arrays sind (die Zeilen).
Zum Beispiel kann ein 4x3-Array von T (wobei "T" irgendein Datentyp ist) enthalten  deklariert werden durch: T a[4][3] , und wie folgt beschrieben  Schema:

%Vor%

Auch die Array-Elemente werden Zeile für Zeile im Speicher gespeichert.
Bevor T vorangestellt wird und [3] an a angehängt wird, haben wir ein Array von 3 Elementen vom Typ T . Der Name a[4] ist jedoch selbst ein Array, das angibt, dass 4 -Elemente jeweils ein Array von 3 -Elementen sind. Daher haben wir ein Array von 4 Arrays von 3 Elementen Jetzt ist klar, dass a auf das erste Element ( a[0] ) von a[4] zeigt. Andererseits wird &a[0] die Adresse des ersten Elements ( a[0] ) von a[4] und &a[0][0] die Adresse von 0th row ( a00 | a01 | a02) von array a[4][3] .% Co_de% wird geben Geben Sie die Adresse des 2D-Arrays &a an. a[3][4] verfällt in Pointer auf *a .
Beachten Sie, dass a[0][0] kein Zeiger auf a ist; stattdessen ist es ein Zeiger auf a[0][0] .
Daher

  
  • G1: a[0] und a sind gleichwertig.
  •   
  • G2: &a[0] , *a und a[0] sind äquivalent.
  •   
  • G3: &a[0][0] (gibt die Adresse des 2D-Arrays &a an).
      Aber die Gruppen G1 , G2 und G3 sind nicht identisch, obwohl sie dasselbe Ergebnis liefern () (und ich oben erklärt habe) warum gibt es dasselbe Ergebnis).
  •   
    
haccks 21.08.2013 15:33
quelle
2

Dies bedeutet auch, dass in C-Arrays kein Overhead vorhanden ist. In einigen anderen Sprachen ist die Struktur von Arrays

%Vor%

und &a != &a[0]

    
Mario Rossi 21.08.2013 15:18
quelle
2
  

Intuitiv ist jetzt der Grund hinter der Ausgabe klar. Aber wenn man bedenkt, wie Zeiger in C implementiert sind, kann ich nicht verstehen, wie a und & amp; a gleich sind. Ich nehme an, dass es eine Variable a im Speicher gibt, die auf das Array zeigt (und die Startadresse dieses Array-Speicherblocks wäre der Wert dieser Variablen a).

Nun, nein. Es gibt keine Adresse, die irgendwo im Speicher gespeichert ist. Es ist nur Speicher für die Rohdaten reserviert, und das ist es. Was passiert, ist, wenn Sie ein nacktes a verwenden, wird sofort in einen Zeiger auf das erste Element zerlegt, wodurch der Eindruck entsteht, dass der 'Wert' von a die Adresse, aber die einzige wäre Wert von a ist der Raw-Array-Speicher.

Tatsächlich sind a und &a unterschiedlich, aber nur im Typ, nicht im Wert. Lassen Sie es uns ein wenig einfacher machen, indem Sie 1D-Arrays verwenden, um diesen Punkt zu verdeutlichen:

%Vor%

Wie Sie sehen, verhalten sich a und &a sehr unterschiedlich, obwohl sie denselben Wert haben.

    
cmaster 14.09.2013 15:40
quelle