Werden in C Arrays Zeiger oder als Zeiger verwendet?

Lesezeit: 9 Minuten

Mein Verständnis war, dass Arrays einfach konstante Zeiger auf eine Folge von Werten waren, und wenn Sie ein Array in C deklarierten, deklarierten Sie einen Zeiger und reservierten Platz für die Folge, auf die es zeigt.

Aber das verwirrt mich: der folgende Code:

char y[20];
char *z = y;

printf("y size is %lu\n", sizeof(y));
printf("y is %p\n", y);
printf("z size is %lu\n", sizeof(z));
printf("z is %p\n", z);

beim Kompilieren mit Apple GCC ergibt sich folgendes Ergebnis:

y size is 20
y is 0x7fff5fbff930
z size is 8
z is 0x7fff5fbff930

(Meine Maschine ist 64 Bit, Zeiger sind 8 Byte lang).

Wenn ‘y’ ein konstanter Zeiger ist, warum hat er dann eine Größe von 20, wie die Folge von Werten, auf die er zeigt? Wird der Variablenname ‘y’ während der Kompilierzeit durch eine Speicheradresse ersetzt, wann immer es angebracht ist? Sind Arrays also eine Art syntaktischer Zucker in C, der beim Kompilieren einfach in Zeiger-Zeug übersetzt wird?

  • mögliches Duplikat einer Frage aus der C++-FAQ der Site: Ist der Array-Name ein Zeiger in C?

    – Steve Jessop

    5. Januar 2011 um 18:17 Uhr


  • Möglicherweise ein Duplikat von: Was ist Array to Point Decay? Diese Frage ist jedoch sowohl als C als auch als C++ gekennzeichnet, sodass die Antworten auch Elemente von C++ enthalten. Daher ist diese Frage möglicherweise kein gutes Duplikat.

    – Andreas Wenzel

    28. August um 8:27 Uhr

  • Möglicherweise ein Duplikat von: Ausnahmen von Arrays, die in einen Zeiger zerfallen?

    – Andreas Wenzel

    28. August um 10:20 Uhr

Hier ist die genaue Sprache aus dem C-Standard (n1256):

6.3.2.1 Lvalues, Arrays und Funktionsbezeichner


3 Außer wenn es der Operand von ist sizeof Operator oder das Unäre & -Operator oder ist ein Zeichenfolgenliteral, das zum Initialisieren eines Arrays verwendet wird, eines Ausdrucks vom Typ „Array of Typ” wird in einen Ausdruck vom Typ ”Zeiger auf Typ”, das auf das Anfangselement des Array-Objekts zeigt und kein Lvalue ist. Wenn das Array-Objekt eine Registerspeicherklasse hat, ist das Verhalten undefiniert.

Es ist wichtig, sich hier daran zu erinnern, dass es einen Unterschied zwischen an gibt Objekt (in C-Begriffen bedeutet das etwas, das Speicher beansprucht) und die Ausdruck verwendet, um auf dieses Objekt zu verweisen.

Wenn Sie ein Array wie z

int a[10];

das Objekt bezeichnet durch die Ausdruck a ist ein Array (d. h. ein zusammenhängender Speicherblock, der groß genug ist, um 10 zu speichern int Werte) und die Art der Ausdruck a ist “10-Element-Array von int“, oder int [10]. Wenn die Ausdruck a erscheint in einem anderen Kontext als als Operand von sizeof oder & Operatoren, dann wird sein Typ implizit konvertiert in int *und sein Wert ist die Adresse des ersten Elements.

Im Falle der sizeof Operator, wenn der Operand ein Ausdruck vom Typ ist T [N]dann ist das Ergebnis die Anzahl der Bytes im Array-Objekt, nicht in einem Zeiger auf dieses Objekt: N * sizeof T.

Im Falle der & -Operator ist der Wert die Adresse des Arrays, die mit der Adresse des ersten Elements des Arrays identisch ist, aber die Typ des Ausdrucks ist anders: angesichts der Deklaration T a[N];der Typ des Ausdrucks &a ist T (*)[N]oder Zeiger auf N-Element-Array von T. The Wert ist das gleiche wie a oder &a[0] (Die Adresse des Arrays ist dieselbe wie die Adresse des ersten Elements im Array), aber der Unterschied in den Typen ist wichtig. Zum Beispiel angesichts des Codes

int a[10];
int *p = a;
int (*ap)[10] = &a;

printf("p = %p, ap = %p\n", (void *) p, (void *) ap);
p++;
ap++;
printf("p = %p, ap = %p\n", (void *) p, (void *) ap);

Sie sehen eine Ausgabe in der Größenordnung von

p = 0xbff11e58, ap = 0xbff11e58
p = 0xbff11e5c, ap = 0xbff11e80

IOW, fortschreitend p fügt hinzu sizeof int (4) auf den ursprünglichen Wert, während fortschreitend ap fügt hinzu 10 * sizeof int (40).

Mehr Standardsprache:

6.5.2.1 Subskription von Arrays

Einschränkungen

1 Einer der Ausdrücke muss vom Typ „Zeiger auf Objekt“ sein Typ”, der andere Ausdruck muss vom Typ Integer sein und das Ergebnis vom Typ ”Typ”.

Semantik

2 Ein Postfix-Ausdruck gefolgt von einem Ausdruck in eckigen Klammern [] ist eine tiefgestellte Bezeichnung eines Elements eines Array-Objekts. Die Definition des Indexoperators [] ist das E1[E2] ist identisch mit (*((E1)+(E2))). Aufgrund der Konvertierungsregeln, die für die Binärdatei gelten + Betreiber, ggf E1 ist ein Array-Objekt (äquivalent ein Zeiger auf das Anfangselement eines Array-Objekts) und E2 ist eine ganze Zahl, E1[E2] bezeichnet die E2-tes Element von E1 (Zählen von Null).

Wenn Sie also einen Array-Ausdruck indizieren, geschieht im Hintergrund, dass der Offset von der Adresse des ersten Elements im Array berechnet und das Ergebnis dereferenziert wird. Der Ausdruck

a[i] = 10;

ist äquivalent zu

*((a)+(i)) = 10;

was äquivalent ist

*((i)+(a)) = 10;

was äquivalent ist

 i[a] = 10;

Ja, die Subskription von Arrays in C ist kommutativ; Um Himmels willen, tun Sie dies niemals im Produktionscode.

Da das Subskriptieren von Arrays in Bezug auf Zeigeroperationen definiert ist, können Sie den Subskriptionsoperator sowohl auf Ausdrücke vom Typ Zeiger als auch vom Typ Array anwenden:

int *p = malloc(sizeof *p * 10);
int i;
for (i = 0; i < 10; i++)
  p[i] = some_initial_value(); 

Hier ist eine praktische Tabelle, um sich an einige dieser Konzepte zu erinnern:

Declaration: T a[N];

Expression    Type    Converts to     Value
----------    ----    ------------    -----
         a    T [N]   T *             Address of the first element in a;
                                        identical to writing &a[0]
        &a    T (*)[N]                Address of the array; value is the same
                                        as above, but the type is different
  sizeof a    size_t                  Number of bytes contained in the array
                                        object (N * sizeof T)
        *a    T                       Value at a[0]
      a[i]    T                       Value at a[i]
     &a[i]    T *                     Address of a[i] 

Declaration: T a[N][M];

Expression     Type        Converts to     Value
----------     ----        ------------    -----
          a    T [N][M]    T (*)[M]        Address of the first subarray (&a[0])
         &a    T (*)[N][M]                 Address of the array (same value as
                                             above, but different type)
   sizeof a    size_t                      Number of bytes contained in the
                                             array object (N * M * sizeof T)
         *a    T [M]      T *              Value of a[0], which is the address
                                             of the first element of the first subarray
                                             (same as &a[0][0])
       a[i]    T [M]      T *              Value of a[i], which is the address
                                             of the first element of the i'th subarray
      &a[i]    T (*)[M]                    Address of the i-th subarray; same value as
                                             above, but different type
sizeof a[i]    size_t                      Number of bytes contained in the i'th subarray
                                             object (M * sizeof T)
      *a[i]    T                           Value of the first element of the i'th 
                                             subarray (a[i][0])
    a[i][j]    T                           Value at a[i][j]
   &a[i][j]    T *                         Address of a[i][j]

Declaration: T a[N][M][O];

Expression        Type             Converts to
----------        ----             -----------
         a        T [N][M][O]      T (*)[M][O]
        &a        T (*)[N][M][O]
        *a        T [M][O]         T (*)[O]
      a[i]        T [M][O]         T (*)[O]
     &a[i]        T (*)[M][O]
     *a[i]        T [O]            T *
   a[i][j]        T [O]            T *
  &a[i][j]        T (*)[O]
  *a[i][j]        T 
a[i][j][k]        T

Von hier aus sollte das Muster für höherdimensionale Arrays klar sein.

Zusammenfassend also: Arrays sind keine Zeiger. In den meisten Kontexten Array Ausdrücke werden in Zeigertypen umgewandelt.

Benutzeravatar von Michael Burr
Michael Burr

Arrays sind keine Zeiger, obwohl in den meisten Ausdrücken ein Arrayname zu einem Zeiger auf das erste Element des Arrays ausgewertet wird. Es ist also sehr, sehr einfach, einen Array-Namen als Zeiger zu verwenden. Sie werden oft den Begriff “Zerfall” sehen, der verwendet wird, um dies zu beschreiben, wie in “das Array ist zu einem Zeiger zerfallen”.

Eine Ausnahme ist als Operand für die sizeof -Operator, wobei das Ergebnis die Größe des Arrays ist (in Bytes, nicht in Elementen).

Ein paar zusätzliche Probleme in diesem Zusammenhang:

Ein Array-Parameter an eine Funktion ist eine Fiktion – der Compiler übergibt wirklich einen einfachen Zeiger (dies gilt nicht für Referenz-auf-Array-Parameter in C++), sodass Sie die tatsächliche Größe eines an eine Funktion übergebenen Arrays nicht bestimmen können – Sie muss diese Informationen auf andere Weise übergeben (möglicherweise mit einem expliziten zusätzlichen Parameter oder mit einem Sentinel-Element – wie es C-Strings tun).

Eine gängige Redewendung, um die Anzahl der Elemente in einem Array zu ermitteln, ist die Verwendung eines Makros wie:

#define ARRAY_SIZE(arr) ((sizeof(arr))/sizeof(arr[0]))

Dies hat das Problem, entweder einen Array-Namen zu akzeptieren, wo es funktioniert, oder einen Zeiger, wo es ohne Warnung des Compilers ein unsinniges Ergebnis liefert. Es gibt sicherere Versionen des Makros (insbesondere für C++), die eine Warnung oder einen Fehler generieren, wenn es mit einem Zeiger anstelle eines Arrays verwendet wird. Siehe die folgenden SO-Elemente:

  • C++-Version
  • eine bessere (wenn auch immer noch nicht ganz sichere) C-Version

Hinweis: C99-VLAs (Arrays mit variabler Länge) befolgen möglicherweise nicht alle diese Regeln (insbesondere können sie als Parameter mit der Arraygröße übergeben werden, die der aufgerufenen Funktion bekannt ist). Ich habe wenig Erfahrung mit VLAs, und soweit ich weiß, sind sie nicht weit verbreitet. Ich möchte jedoch darauf hinweisen, dass die obige Diskussion möglicherweise anders auf VLAs zutrifft.

  • also wurde die sizeof-Ausnahme gemacht, weil sie nützlich war … ich wusste nicht, dass es tatsächlich eine Möglichkeit gibt, die Größe eines Arrays zu kennen! (obwohl es immer noch nicht so nützlich ist, weil es nur die Größe von Arrays mit fester Größe findet, aber ich denke, es ist besser, als viele Konstanten für denselben Zweck zu definieren)

    – Salvador p

    6. Januar 2011 um 18:17 Uhr

Benutzeravatar von Péter Török
Peter Török

sizeof wird zur Kompilierzeit ausgewertet, und der Compiler weiß, ob der Operand ein Array oder ein Zeiger ist. Bei Arrays gibt es die Anzahl der vom Array belegten Bytes an. Ihr Array ist a char[] (und sizeof(char) ist 1), also sizeof passiert, um Ihnen die Anzahl der Elemente zu geben. Um die Anzahl der Elemente im allgemeinen Fall zu erhalten, ist eine gebräuchliche Redewendung (hier z int):

int y[20];
printf("number of elements in y is %lu\n", sizeof(y) / sizeof(int));

Für Zeiger sizeof gibt die Anzahl der vom Rohzeigertyp belegten Bytes an.

Im

char hello[] = "hello there"
int i;

und

char* hello = "hello there";
int i;

In der ersten Instanz (Discounting Alignment) werden 12 Bytes für hallo gespeichert, wobei der zugeordnete Platz initialisiert wird Hallo während in der zweiten Hallo wird an anderer Stelle gespeichert (möglicherweise statischer Raum) und hello wird initialisiert, um auf die angegebene Zeichenfolge zu zeigen.

hello[2] ebenso gut wie *(hello + 2) gibt jedoch in beiden Fällen ‘e’ zurück.

Zusätzlich zu dem, was die anderen gesagt haben, hilft vielleicht dieser Artikel: http://en.wikipedia.org/wiki/C_%28programming_language%29#Array-pointer_interchangeability

Benutzeravatar von Andrew Sledge
Andreas Schlitten

Wenn ‘y’ ein konstanter Zeiger ist, warum hat er dann eine Größe von 20, wie die Folge von Werten, auf die er zeigt?

Da z ist die Adresse der Variablen und gibt für Ihre Maschine immer 8 zurück. Sie müssen den Dereferenzierungszeiger (&) verwenden, um den Inhalt einer Variablen zu erhalten.

EDIT: Eine gute Unterscheidung zwischen den beiden: http://www.cs.cf.ac.uk/Dave/C/node10.html

1412660cookie-checkWerden in C Arrays Zeiger oder als Zeiger verwendet?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy