Ist ein Array-Name ein Zeiger?

203

Ist der Name eines Arrays ein Zeiger in C? Wenn nicht, was ist der Unterschied zwischen dem Namen eines Arrays und einer Zeigervariablen?

Lundin
quelle
4
Nein, aber das Array ist das gleiche & Array [0]
36
@pst: &array[0]ergibt einen Zeiger, kein Array;)
Jalf
28
@Nava (und pst): Array und & Array [0] sind nicht wirklich gleich. Ein typisches Beispiel : sizeof (Array) und sizeof (& array [0]) ergeben unterschiedliche Ergebnisse.
Thomas Padron-McCarthy
1
@Thomas stimmt zu, aber in Bezug auf Zeiger erzeugen sie, wenn Sie Array und & array [0] dereferenzieren, den gleichen Wert von array [0] .ie * array == array [0]. Niemand hat gemeint, dass diese beiden Zeiger gleich sind, aber in diesem speziellen Fall (auf das erste Element zeigend) können Sie auch den Namen des Arrays verwenden.
Nava Carmon
1
Dies könnte auch zu Ihrem Verständnis beitragen : stackoverflow.com/questions/381542 , stackoverflow.com/questions/660752
Dinah

Antworten:

255

Ein Array ist ein Array und ein Zeiger ist ein Zeiger, aber in den meisten Fällen werden Array-Namen in Zeiger konvertiert . Ein häufig verwendeter Begriff ist, dass sie zu Zeigern zerfallen .

Hier ist ein Array:

int a[7];

a enthält Platz für sieben Ganzzahlen, und Sie können einen Wert mit einer Zuweisung wie folgt in eine von ihnen einfügen:

a[3] = 9;

Hier ist ein Zeiger:

int *p;

pEnthält keine Leerzeichen für Ganzzahlen, kann jedoch auf ein Leerzeichen für eine Ganzzahl verweisen. Wir können es beispielsweise so einstellen, dass es auf eine der Stellen im Array zeigt a, z. B. die erste:

p = &a[0];

Was verwirrend sein kann, ist, dass Sie auch Folgendes schreiben können:

p = a;

Dies gilt nicht kopieren Sie den Inhalt des Arrays ain den Zeiger p(was auch immer das bedeuten würde). Stattdessen wird der Array-Name ain einen Zeiger auf sein erstes Element konvertiert. Diese Zuweisung erfolgt also genauso wie die vorherige.

Jetzt können Sie pauf ähnliche Weise wie ein Array verwenden:

p[3] = 17;

Der Grund dafür ist, dass der Array-Dereferenzierungsoperator in C ,, [ ]in Form von Zeigern definiert ist. x[y]bedeutet: Beginnen Sie mit dem Zeiger x, yschreiten Sie nach dem, worauf der Zeiger zeigt, vorwärts und nehmen Sie dann das, was sich dort befindet. Unter Verwendung der Zeigerarithmetiksyntax x[y]kann auch geschrieben werden als *(x+y).

Damit dies mit einem normalen Array wie unserem funktioniert a, muss der Name ain a[3]zuerst in einen Zeiger (auf das erste Element in a) konvertiert werden . Dann treten wir 3 Elemente vor und nehmen, was auch immer da ist. Mit anderen Worten: Nehmen Sie das Element an Position 3 im Array. (Welches ist das vierte Element im Array, da das erste mit 0 nummeriert ist.)

Zusammenfassend werden Array-Namen in einem C-Programm (in den meisten Fällen) in Zeiger konvertiert. Eine Ausnahme ist, wenn wir den sizeofOperator für ein Array verwenden. Wenn ain diesem Kontext in einen Zeiger konvertiert sizeof awürde , würde dies die Größe eines Zeigers und nicht des tatsächlichen Arrays angeben, was ziemlich nutzlos wäre, also in diesem Fall adas Array selbst bedeutet.

Thomas Padron-McCarthy
quelle
5
Eine ähnliche automatische Konvertierung wird auf Funktionszeiger angewendet - beide functionpointer()und (*functionpointer)()bedeuten seltsamerweise dasselbe.
Carl Norum
3
Er fragte nicht, ob Arrays und Zeiger gleich sind, sondern ob der Name eines Arrays ein Zeiger ist
Ricardo Amores
32
Ein Array-Name ist kein Zeiger. Es ist ein Bezeichner für eine Variable vom Typ Array, die implizit in einen Zeiger vom Elementtyp konvertiert wird.
Pavel Minaev
29
Abgesehen davon sizeof()ist der andere Kontext, in dem es keinen Array-> Zeigerzerfall gibt, der Operator &- in Ihrem obigen Beispiel &aist er ein Zeiger auf ein Array von 7 int, kein Zeiger auf ein einzelnes int; das heißt, sein Typ wird sein int(*)[7], der nicht implizit in konvertierbar ist int*. Auf diese Weise können Funktionen tatsächlich Zeiger auf Arrays bestimmter Größe nehmen und die Einschränkung über das Typsystem erzwingen.
Pavel Minaev
3
@ onmyway133, hier finden Sie eine kurze Erklärung und weitere Zitate.
Carl Norum
36

Wenn ein Array als Wert verwendet wird, repräsentiert sein Name die Adresse des ersten Elements.
Wenn ein Array nicht als Wert verwendet wird, repräsentiert sein Name das gesamte Array.

int arr[7];

/* arr used as value */
foo(arr);
int x = *(arr + 1); /* same as arr[1] */

/* arr not used as value */
size_t bytes = sizeof arr;
void *q = &arr; /* void pointers are compatible with pointers to any object */
pmg
quelle
20

Wenn ein Ausdruck von Array - Typ (wie beispielsweise den Array Namen) wird in einem größeren Ausdruck und es ist nicht der Operand entweder den &oder sizeofOperatoren, dann wird der Typ des Array - Ausdrucks umgewandelt von „N-Element - Array von T“ "Zeiger auf T", und der Wert des Ausdrucks ist die Adresse des ersten Elements im Array.

Kurz gesagt, der Array-Name ist kein Zeiger, aber in den meisten Kontexten wird er so behandelt, als wäre er ein Zeiger.

Bearbeiten

Beantwortung der Frage im Kommentar:

Wenn ich sizeof verwende, zähle ich nur die Größe der Elemente des Arrays? Dann nimmt das Array "head" auch Platz mit den Informationen über die Länge und einen Zeiger ein (und dies bedeutet, dass es mehr Platz benötigt als ein normaler Zeiger)?

Wenn Sie ein Array erstellen, wird nur der Speicherplatz für die Elemente selbst zugewiesen. Für einen separaten Zeiger oder Metadaten wird kein Speicher materialisiert. Gegeben

char a[10];

Was Sie in Erinnerung behalten, ist

   +---+
a: |   | a[0]
   +---+ 
   |   | a[1]
   +---+
   |   | a[2]
   +---+
    ...
   +---+
   |   | a[9]
   +---+

Der Ausdruck a bezieht sich auf das gesamte Array, aber es gibt kein Objekt, a das von den Array-Elementen selbst getrennt ist. Somit erhalten sizeof aSie die Größe (in Bytes) des gesamten Arrays. Der Ausdruck &agibt Ihnen die Adresse des Arrays an, die mit der Adresse des ersten Elements übereinstimmt . Der Unterschied zwischen &aund &a[0]ist der Typ des Ergebnisses 1 - char (*)[10]im ersten und char *im zweiten Fall .

Seltsam wird es, wenn Sie auf einzelne Elemente zugreifen möchten - der Ausdruck a[i]wird als Ergebnis von definiert *(a + i)-, wenn ein Adresswert angegeben wird a, iElemente ( keine Bytes ) von dieser Adresse versetzt werden und das Ergebnis dereferenziert wird.

Das Problem ist, dass aes sich nicht um einen Zeiger oder eine Adresse handelt, sondern um das gesamte Array-Objekt. Daher gilt in C die Regel, dass der Compiler immer dann, wenn er einen Ausdruck vom Typ Array sieht (z. B. aTyp)char [10] ) und dieser Ausdruck nicht der Operand der sizeofoder unären &Operatoren ist, der Typ dieses Ausdrucks konvertiert wird ("Zerfälle"). auf einen Zeigertyp ( char *), und der Wert des Ausdrucks ist die Adresse des ersten Elements des Arrays. Daher hat der Ausdruck a den gleichen Typ und Wert wie der Ausdruck &a[0](und im weiteren Sinne hat der Ausdruck *aden gleichen Typ und Wert wie der Ausdruck a[0]).

C aus einer früheren Sprache abgeleitet wurde B genannt, und in B a war ein separater Zeiger Objekt von den Array - Elementen a[0], a[1]etc. Ritchie wollte B Array Semantik halten, aber er wollte mich nicht mit dem separaten Zeigerobjekt zu speichern. Also wurde er es los. Stattdessen konvertiert der Compiler bei Bedarf während der Übersetzung Array-Ausdrücke in Zeigerausdrücke.

Denken Sie daran, dass Arrays keine Metadaten über ihre Größe speichern. Sobald dieser Array-Ausdruck zu einem Zeiger "zerfällt", haben Sie nur noch einen Zeiger auf ein einzelnes Element. Dieses Element kann das erste einer Folge von Elementen sein, oder es kann ein einzelnes Objekt sein. Es gibt keine Möglichkeit, anhand des Zeigers selbst zu wissen.

Wenn Sie einen Array-Ausdruck an eine Funktion übergeben, erhält die Funktion nur einen Zeiger auf das erste Element - sie hat keine Ahnung, wie groß das Array ist (aus diesem Grund war die getsFunktion eine solche Bedrohung und wurde schließlich aus der Bibliothek entfernt). Damit die Funktion weiß, wie viele Elemente das Array enthält, müssen Sie entweder einen Sentinel-Wert verwenden (z. B. den 0-Terminator in C-Zeichenfolgen) oder die Anzahl der Elemente als separaten Parameter übergeben.


  1. Welche * Auswirkungen * auf die Interpretation des Adresswerts haben können - hängt von der Maschine ab.
John Bode
quelle
Ich habe lange nach dieser Antwort gesucht. Danke dir! Und wenn Sie wissen, können Sie etwas weiter sagen, was ein Array-Ausdruck ist. Wenn ich sizeof verwende, zähle ich nur die Größe der Elemente des Arrays? Dann nimmt das Array "head" auch Platz mit den Informationen über die Länge und einen Zeiger ein (und dies bedeutet, dass es mehr Platz benötigt als ein normaler Zeiger)?
Andriy Dmytruk
Und noch etwas. Ein Array der Länge 5 ist vom Typ int [5]. Woher kennen wir also die Länge, wenn wir sizeof (Array) aufrufen - von seinem Typ? Und das bedeutet, dass Arrays unterschiedlicher Länge wie verschiedene Arten von Konstanten sind?
Andriy Dmytruk
@AndriyDmytruk: sizeofist ein Operator und wertet die Anzahl der Bytes im Operanden aus (entweder ein Ausdruck, der ein Objekt bezeichnet, oder ein Typname in Klammern). Für ein Array wird also sizeofdie Anzahl der Elemente multipliziert mit der Anzahl der Bytes in einem einzelnen Element ausgewertet. Wenn an int4 Bytes breit ist, nimmt ein 5-Elemente-Array von int20 Bytes ein.
John Bode
Ist der Betreiber nicht auch etwas [ ]Besonderes? Zum Beispiel wird hier int a[2][3];für x = a[1][2];, obwohl es umgeschrieben werden kann x = *( *(a+1) + 2 );, anicht in einen Zeigertyp konvertiert int*(obwohl, wenn aes sich um ein Argument einer Funktion handelt, in die es konvertiert werden sollte int*).
Stan
2
@Stan: Der Ausdruck ahat einen Typ int [2][3], der zum Typ "zerfällt" int (*)[3]. Der Ausdruck *(a + 1)hat einen Typ int [3], der zu "zerfällt" int *. Somit *(*(a + 1) + 2)wird Typ haben int. azeigt auf das erste 3-Element-Array von int, a + 1zeigt auf das zweite 3-Element-Array von int, *(a + 1) ist das zweite 3-Element-Array von int, *(a + 1) + 2zeigt auf das dritte Element des zweiten Arrays von int, so *(*(a + 1) + 2) ist das dritte Element des zweiten Arrays von int. Wie das dem Maschinencode zugeordnet wird, liegt ganz beim Compiler.
John Bode
5

Ein so deklariertes Array

int a[10];

reserviert Speicher für 10 ints. Sie können nicht ändern, aaber Sie können Zeigerarithmetik mit a.

Ein solcher Zeiger weist nur dem Zeiger Speicher zu p:

int *p;

Es werden keine ints zugewiesen . Sie können es ändern:

p = a;

und verwenden Sie Array-Indizes wie möglich mit:

p[2] = 5;
a[2] = 5;    // same
*(p+2) = 5;  // same effect
*(a+2) = 5;  // same effect
Grumdrig
quelle
2
Arrays werden nicht immer auf dem Stapel zugewiesen. Yhat ist ein Implementierungsdetail, das von Compiler zu Compiler unterschiedlich sein wird. In den meisten Fällen werden statische oder globale Arrays aus einem anderen Speicherbereich als dem Stapel zugewiesen. Arrays von Konstantentypen können aus einer weiteren Speicherregion zugewiesen werden
Mark Bessey,
1
Ich denke, Grumdrig wollte sagen "weist 10 ints mit automatischer Speicherdauer zu".
Leichtigkeitsrennen im Orbit
4

Der Arrayname selbst ergibt einen Speicherort, sodass Sie den Arraynamen wie einen Zeiger behandeln können:

int a[7];

a[0] = 1976;
a[1] = 1984;

printf("memory location of a: %p", a);

printf("value at memory location %p is %d", a, *a);

Und andere nützliche Dinge, die Sie mit dem Zeiger tun können (z. B. Hinzufügen / Entfernen eines Versatzes), können Sie auch mit einem Array tun:

printf("value at memory location %p is %d", a + 1, *(a + 1));

In Bezug auf die Sprache, wenn C das Array nicht nur als eine Art "Zeiger" verfügbar gemacht hat (pedantisch ist es nur ein Speicherort. Es kann nicht auf einen beliebigen Speicherort im Speicher verweisen oder vom Programmierer gesteuert werden). Wir müssen dies immer codieren:

printf("value at memory location %p is %d", &a[1], a[1]);
Michael Buen
quelle
1

Ich denke, dieses Beispiel wirft ein Licht auf das Thema:

#include <stdio.h>
int main()
{
        int a[3] = {9, 10, 11};
        int **b = &a;

        printf("a == &a: %d\n", a == b);
        return 0;
}

Es kompiliert gut (mit 2 Warnungen) in gcc 4.9.2 und druckt Folgendes:

a == &a: 1

Hoppla :-)

Die Schlussfolgerung lautet also nein, das Array ist kein Zeiger, es wird nicht als Zeiger im Speicher (nicht einmal schreibgeschützt) gespeichert, obwohl es so aussieht, da es seine Adresse mit dem Operator & abrufen kann . Aber - oops - dieser Operator funktioniert nicht :-)), so oder so, Sie wurden gewarnt:

p.c: In function main’:
pp.c:6:12: warning: initialization from incompatible pointer type
  int **b = &a;
            ^
p.c:8:28: warning: comparison of distinct pointer types lacks a cast
  printf("a == &a: %d\n", a == b);

C ++ lehnt solche Versuche mit Fehlern in der Kompilierungszeit ab.

Bearbeiten:

Das wollte ich demonstrieren:

#include <stdio.h>
int main()
{
    int a[3] = {9, 10, 11};
    void *c = a;

    void *b = &a;
    void *d = &c;

    printf("a == &a: %d\n", a == b);
    printf("c == &c: %d\n", c == d);
    return 0;
}

Obwohl cund a"zeigen" Sie auf denselben Speicher, können Sie die Adresse des cZeigers erhalten, aber Sie können die Adresse des aZeigers nicht erhalten .

Palo
quelle
1
"Es kompiliert gut (mit 2 Warnungen)". Das ist nicht gut Wenn Sie gcc anweisen, es durch Hinzufügen als richtigen Standard-C zu kompilieren -std=c11 -pedantic-errors, wird ein Compilerfehler beim Schreiben von ungültigem C-Code angezeigt. Der Grund dafür ist, dass Sie versuchen, int (*)[3]einer Variablen von a zuzuweisen. Dies int**sind zwei Typen, die absolut nichts miteinander zu tun haben. Was dieses Beispiel beweisen soll, weiß ich nicht.
Lundin
Danke Lundin für deinen Kommentar. Sie wissen, dass es viele Standards gibt. Ich habe versucht zu klären, was ich in der Bearbeitung meinte. Der int **Typ ist dort nicht der Punkt, man sollte den besser dafür verwenden void *.
Palo
-3

Der Arrayname verhält sich wie ein Zeiger und zeigt auf das erste Element des Arrays. Beispiel:

int a[]={1,2,3};
printf("%p\n",a);     //result is similar to 0x7fff6fe40bc0
printf("%p\n",&a[0]); //result is similar to 0x7fff6fe40bc0

Beide print-Anweisungen geben für eine Maschine genau die gleiche Ausgabe aus. In meinem System gab es:

0x7fff6fe40bc0
Amitesh Ranjan
quelle
-4

Ein Array ist eine Sammlung von aufeinanderfolgenden und zusammenhängenden Elementen im Speicher. In C ist der Name eines Arrays der Index für das erste Element. Wenn Sie einen Versatz anwenden, können Sie auf die restlichen Elemente zugreifen. Ein "Index zum ersten Element" ist in der Tat ein Zeiger auf eine Speicherrichtung.

Der Unterschied zu Zeigervariablen besteht darin, dass Sie die Position, auf die der Name des Arrays zeigt, nicht ändern können. Sie ähnelt also einem konstanten Zeiger (ähnlich, nicht identisch. Siehe Marks Kommentar). Aber auch, dass Sie den Array-Namen nicht dereferenzieren müssen, um den Wert zu erhalten, wenn Sie Zeigeraritmetik verwenden:

char array = "hello wordl";
char* ptr = array;

char c = array[2]; //array[2] holds the character 'l'
char *c1 = ptr[2]; //ptr[2] holds a memory direction that holds the character 'l'

Die Antwort lautet also irgendwie "Ja".

Ricardo Amores
quelle
1
Ein Array-Name ist nicht mit einem const-Zeiger identisch. Gegeben: int a [10]; int * p = a; Größe von (p) und Größe von (a) sind nicht gleich.
Mark Bessey
1
Es gibt andere Unterschiede. Im Allgemeinen ist es am besten, sich an die vom C-Standard verwendete Terminologie zu halten, die ihn speziell als "Konvertierung" bezeichnet. Zitat: "Außer wenn es sich um den Operanden des Operators sizeof oder des Operators unary & handelt oder um ein Zeichenfolgenliteral, das zum Initialisieren eines Arrays verwendet wird, wird ein Ausdruck vom Typ '' Array vom Typ '' in einen Ausdruck vom Typ 'konvertiert. 'Zeiger auf Typ' ', der auf das Anfangselement des Array-Objekts zeigt und kein Wert ist. Wenn das Array-Objekt eine Registerspeicherklasse hat, ist das Verhalten undefiniert. "
Pavel Minaev
-5

Der Array-Name ist die Adresse des ersten Elements eines Arrays. Der Array-Name ist also ein konstanter Zeiger.

SAQIB SOHAIL BHATTI
quelle