Wie kommt es, dass die Adresse eines Arrays gleich dem Wert in C ist?

189

Im folgenden Codebit unterscheiden sich Zeigerwerte und Zeigeradressen wie erwartet.

Array-Werte und Adressen jedoch nicht!

Wie kann das sein?

Ausgabe

my_array = 0022FF00
&my_array = 0022FF00
pointer_to_array = 0022FF00
&pointer_to_array = 0022FEFC
#include <stdio.h>

int main()
{
  char my_array[100] = "some cool string";
  printf("my_array = %p\n", my_array);
  printf("&my_array = %p\n", &my_array);

  char *pointer_to_array = my_array;
  printf("pointer_to_array = %p\n", pointer_to_array);
  printf("&pointer_to_array = %p\n", &pointer_to_array);

  printf("Press ENTER to continue...\n");
  getchar();
  return 0;
}
Alexandre
quelle
Aus den häufig gestellten Fragen zu comp.lang.c: - [Was ist also mit der Äquivalenz von Zeigern und Arrays in C gemeint? ] ( c-faq.com/aryptr/aryptrequiv.html ) - [Da Array-Referenzen in Zeiger zerfallen, was ist der Unterschied zwischen arr und & arr, wenn arr ein Array ist? ] ( c-faq.com/aryptr/aryvsadr.html ) Oder lesen Sie den gesamten Abschnitt Arrays and Pointers .
Jamesdlin
3
Ich hatte vor zwei Jahren hier eine Antwort mit Diagramm zu dieser Frage hinzugefügt. Was kommt sizeof(&array)zurück?
Grijesh Chauhan

Antworten:

212

Der Name eines Arrays wertet üblicherweise an die Adresse des ersten Elements des Arrays, so arrayund &arrayhat den gleichen Wert (aber verschiedene Typen, so array+1und &array+1wird nicht gleich sein , wenn die Anordnung mehr als 1 Element ist lang).

Hiervon gibt es zwei Ausnahmen: Wenn der Array-Name ein Operand von sizeofoder unär &(Adresse von) ist, bezieht sich der Name auf das Array-Objekt selbst. Auf diese Weise erhalten sizeof arraySie die Größe des gesamten Arrays in Bytes und nicht die Größe eines Zeigers.

Für ein Array, das als definiert ist T array[size], hat es den Typ T *. Wenn Sie es erhöhen, gelangen Sie zum nächsten Element im Array.

&arrayWird an derselben Adresse ausgewertet, aber mit derselben Definition wird ein Zeiger vom Typ erstellt, T(*)[size]dh es ist ein Zeiger auf ein Array und nicht auf ein einzelnes Element. Wenn Sie diesen Zeiger erhöhen, wird die Größe des gesamten Arrays hinzugefügt, nicht die Größe eines einzelnen Elements. Zum Beispiel mit Code wie diesem:

char array[16];
printf("%p\t%p", (void*)&array, (void*)(&array+1));

Wir können erwarten, dass der zweite Zeiger 16 größer als der erste ist (da es sich um ein Array mit 16 Zeichen handelt). Da% p Zeiger normalerweise hexadezimal konvertiert, sieht es möglicherweise so aus:

0x12341000    0x12341010
Jerry Sarg
quelle
3
@Alexandre: &arrayist ein Zeiger auf das erste Element des Arrays, wobei sich as arrayauf das gesamte Array bezieht. Der grundlegende Unterschied kann auch durch einen Vergleich beobachtet werden sizeof(array), zu sizeof(&array). Beachten Sie jedoch, dass nur, wenn Sie arrayals Argument an eine Funktion übergeben, &arraytatsächlich nur übergeben wird. Sie können ein Array nur dann als Wert übergeben, wenn es mit a gekapselt ist struct.
Clifford
14
@Clifford: Wenn Sie ein Array an eine Funktion übergeben, zerfällt es in einen Zeiger auf das erste Element, das so effektiv übergeben &array[0]wird, und nicht &arrayin einen Zeiger auf das Array. Es mag ein Trottel sein, aber ich denke, es ist wichtig, klar zu machen; Compiler werden warnen, wenn die Funktion einen Prototyp hat, der dem Typ des übergebenen Zeigers entspricht.
CB Bailey
2
@ Jerry Coffin Zum Beispiel int * p = & a, wenn ich die Speicheradresse des int-Zeigers p möchte, kann ich & p tun. Da & Array in die Adresse des gesamten Arrays konvertiert (die an der Adresse des ersten Elements beginnt). Wie finde ich dann die Speicheradresse des Array-Zeigers (der die Adresse des ersten Elements im Array speichert)? Es muss irgendwo in der Erinnerung sein, oder?
John Lee
2
@ JohnLee: Nein, es muss nirgendwo im Speicher einen Zeiger auf das Array geben. Wenn Sie einen Zeiger erstellen, können Sie dessen Adresse übernehmen : int *p = array; int **pp = &p;.
Jerry Coffin
3
@ Clifford der erste Kommentar ist falsch, warum immer noch behalten? Ich denke, es könnte zu Missverständnissen für diejenigen führen, die die folgende Antwort (@Charles) nicht lesen.
Rick
30

Dies liegt daran, dass sich der Array-Name ( my_array) von einem Zeiger auf ein Array unterscheidet. Es ist ein Alias ​​für die Adresse eines Arrays, und seine Adresse ist als die Adresse des Arrays selbst definiert.

Der Zeiger ist jedoch eine normale C-Variable auf dem Stapel. Auf diese Weise können Sie die Adresse übernehmen und einen anderen Wert als die darin enthaltene Adresse abrufen.

Ich schrieb über dieses Thema hier - bitte werfen Sie einen Blick.

Eli Bendersky
quelle
Sollte & my_array nicht eine ungültige Operation sein, da der Wert von my_array nicht auf dem Stapel liegt, sondern nur my_array [0 ... Länge]? Dann würde alles Sinn machen ...
Alexandre
@Alexandre: Ich bin mir nicht sicher, warum das eigentlich erlaubt ist.
Eli Bendersky
Sie können die Adresse jeder Variablen (falls nicht markiert register) unabhängig von ihrer Speicherdauer verwenden: statisch, dynamisch oder automatisch.
CB Bailey
my_arrayselbst ist auf dem Stapel, weil my_array ist das gesamte Array.
Café
3
my_array, wenn nicht das Subjekt der Operatoren &oder sizeof, wird zu einem Zeiger auf sein erstes Element (dh &my_array[0]) ausgewertet - aber my_arrayselbst ist nicht dieser Zeiger ( my_arrayist immer noch das Array). Dieser Zeiger ist nur ein kurzlebiger Wert (z. B. gegeben int a;, es ist genau so a + 1) - zumindest konzeptionell wird er "nach Bedarf berechnet". Der wahre "Wert" von my_arrayist der Inhalt des gesamten Arrays - es ist nur so, dass das Festhalten dieses Werts in C dem Versuch gleicht, Nebel in einem Glas aufzufangen.
Café
27

Wenn Sie in C den Namen eines Arrays in einem Ausdruck verwenden (einschließlich der Übergabe an eine Funktion), zerfällt es in einen Zeiger auf sein erstes Element , es sei denn, es ist der Operand des &Operators address-of ( ) oder des sizeofOperators .

Das heißt, in den meisten Kontexten arrayentspricht dies &array[0]sowohl dem Typ als auch dem Wert.

In Ihrem Beispiel my_arrayhat Typ, char[100]der zu a zerfällt, char*wenn Sie ihn an printf übergeben.

&my_arrayhat Typ char (*)[100](Zeiger auf Array von 100 char). Da es sich um den Operanden handelt, &ist dies einer der Fälle, my_arrayin denen ein Zeiger auf sein erstes Element nicht sofort verfällt.

Der Zeiger auf das Array hat den gleichen Adresswert wie ein Zeiger auf das erste Element des Arrays, da ein Array-Objekt nur eine zusammenhängende Folge seiner Elemente ist, aber ein Zeiger auf ein Array hat einen anderen Typ als ein Zeiger auf ein Element von dieses Array. Dies ist wichtig, wenn Sie Zeigerarithmetik für die beiden Zeigertypen ausführen.

pointer_to_arrayhat type char *- initialisiert, um auf das erste Element des Arrays zu zeigen, da dies my_arrayim Initialisierungsausdruck zerfällt - und &pointer_to_array hat type char **(Zeiger auf einen Zeiger auf a char).

Von diesen: my_array(nach dem Zerfall auf char*) &my_arrayund pointer_to_arrayalle zeigen direkt auf das Array oder das erste Element des Arrays und haben daher den gleichen Adresswert.

CB Bailey
quelle
3

Der Grund, warum my_arrayund das &my_arrayErgebnis zu derselben Adresse führt, kann leicht verstanden werden, wenn Sie sich das Speicherlayout eines Arrays ansehen.

Angenommen, Sie haben ein Array mit 10 Zeichen (stattdessen die 100 in Ihrem Code).

char my_array[10];

Speicher für my_arraysieht ungefähr so ​​aus:

+---+---+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+---+---+
^
|
Address of my_array.

In C / C ++ zerfällt ein Array in den Zeiger auf das erste Element in einem Ausdruck wie z

printf("my_array = %p\n", my_array);

Wenn Sie untersuchen, wo das erste Element des Arrays liegt, werden Sie feststellen, dass seine Adresse mit der Adresse des Arrays übereinstimmt:

my_array[0]
|
v
+---+---+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+---+---+
^
|
Address of my_array[0].
R Sahu
quelle
3

In der Programmiersprache B, die der unmittelbare Vorgänger von C war, waren Zeiger und ganze Zahlen frei austauschbar. Das System würde sich so verhalten, als wäre der gesamte Speicher ein riesiges Array. Jedem Variablennamen war entweder eine globale oder eine stapelrelative Adresse zugeordnet. Für jeden Variablennamen musste der Compiler lediglich nachverfolgen, ob es sich um eine globale oder lokale Variable handelte, und seine Adresse relativ zur ersten globalen oder lokalen Variable.

Bei einer globalen Deklaration wie i;[es war nicht erforderlich, einen Typ anzugeben, da alles eine Ganzzahl / ein Zeiger war] würde der Compiler als: address_of_i = next_global++; memory[address_of_i] = 0;und eine Anweisung wie i++als: verarbeitet memory[address_of_i] = memory[address_of_i]+1;.

Eine Erklärung wie arr[10];würde als verarbeitet werden address_of_arr = next_global; memory[next_global] = next_global; next_global += 10;. Beachten Sie, dass der Compiler sofort vergessen kann arr, ein Array zu sein , sobald diese Deklaration verarbeitet wurde . Eine Anweisung wie arr[i]=6;würde als verarbeitet werden memory[memory[address_of_a] + memory[address_of_i]] = 6;. Dem Compiler ist es egal, ob arrer ein Array und ieine Ganzzahl darstellt oder umgekehrt. In der Tat wäre es egal, ob sie beide Arrays oder beide Ganzzahlen wären; Es würde den Code wie beschrieben sehr gerne generieren, ohne Rücksicht darauf, ob das resultierende Verhalten wahrscheinlich nützlich wäre.

Eines der Ziele der Programmiersprache C war es, weitgehend mit B kompatibel zu sein. In B identifizierte der Name eines Arrays [in der Terminologie von B als "Vektor" bezeichnet] eine Variable, die einen Zeiger enthielt, auf den ursprünglich gezeigt wurde auf das erste Element einer Zuordnung der angegebenen Größe. Wenn dieser Name also in der Argumentliste für eine Funktion erscheint, erhält die Funktion einen Zeiger auf den Vektor. Obwohl C "echte" Array-Typen hinzufügte, deren Name fest mit der Adresse der Zuordnung verknüpft war und nicht mit einer Zeigervariablen, die anfänglich auf die Zuordnung verweisen würde, zerlegen sich Arrays in Zeiger, deren Code, der ein Array vom Typ C deklariert, sich identisch verhält zu B-Code, der einen Vektor deklariert und dann die Variable, die seine Adresse enthält, nie geändert hat.

Superkatze
quelle
1

Eigentlich &myarrayund myarraybeide sind die Basisadresse.

Wenn Sie den Unterschied sehen möchten, anstatt zu verwenden

printf("my_array = %p\n", my_array);
printf("my_array = %p\n", &my_array);

verwenden

printf("my_array = %s\n", my_array);
printf("my_array = %p\n", my_array);
Ravi Bisla
quelle