Zeigeradresse in einem mehrdimensionalen C-Array

Lesezeit: 7 Minuten

Ich spiele mit mehrdimensionalen Arrays und Zeigern herum. Ich habe mir ein Programm angesehen, das den Inhalt und die Adressen eines einfachen Arrays ausgibt. Hier ist meine Array-Deklaration:

int zippo[4][2] = { {2,4},
            {6,8},
            {1,3},
            {5,7}   };

Mein aktuelles Verständnis ist das zippo ist ein Zeiger und kann die Adresse einiger anderer Zeiger enthalten. Standardmäßig, zippo enthält die Adresse des Zeigers zippo[0]und es kann auch die Adressen von Zeigern enthalten zippo[1], zippo[2]und zippo[3].

Nehmen Sie nun die folgende Aussage:

printf("zippo[0] = %p\n", zippo[0]);
printf("  *zippo = %p\n", *zippo);
printf("   zippo = %p\n", zippo);

Auf meinem Rechner ergibt das folgende Ausgabe:

zippo[0] = 0x7fff170e2230
  *zippo = 0x7fff170e2230
   zippo = 0x7fff170e2230

Ich verstehe vollkommen warum zippo[0] und *zippo denselben Wert haben. Sie sind beide Zeiger und speichern (standardmäßig) die Adresse der Ganzzahl 2 oder zippo[0][0]. Aber was ist los mit zippo auch die gleiche Speicheradresse teilen? Sollte nicht zippo die Adresse des Zeigers speichern zippo[0]? Waaaas?

  • Eigentlich habe ich Java nie angerührt. Ich habe mit BASIC und Pascal kurz die Zehen ins Wasser gesteckt, bin dann mit C gleich reingesprungen. C ist die erste und einzige Sprache, die ich jemals wirklich versucht habe zu lernen. Was, ist Java viel höher und muss sich daher nicht mit dieser Detailebene befassen?

    – Ichimonji10

    5. Januar 2010 um 16:27 Uhr

Wenn in den meisten Kontexten ein Array-Ausdruck erscheint, wird sein Typ implizit von „N-Element-Array von T“ in „Zeiger auf T“ konvertiert und sein Wert wird so festgelegt, dass er auf das erste Element im Array zeigt. Die Ausnahmen von dieser Regel sind, wenn der Array-Ausdruck ein Operand von entweder ist sizeof oder Adresse von (&)-Operatoren oder wenn das Array ein Zeichenfolgenliteral ist, das als Initialisierer in einer Deklaration verwendet wird.

So der Ausdruck zippo “zerfällt” vom Typ int [4][2] (4-Element-Array von 2-Element-Arrays von int) zu int (*)[2] (Zeiger auf 2-Element-Array von int). Ebenso die Art der zippo[0] ist int [2]die implizit in umgewandelt wird int *.

Angesichts der Erklärung int zippo[4][2]zeigt die folgende Tabelle die Typen verschiedener Array-Ausdrücke mit Zippo und allen impliziten Konvertierungen:

Expression    Type            Implicitly converted to  Equivalent expression
----------    ----            -----------------------  ---------------------
zippo         int [4][2]      int (*)[2]               
&zippo        int (*)[4][2]       
*zippo        int [2]         int *                    zippo[0]
zippo[i]      int [2]         int *
&zippo[i]     int (*)[2]                               
*zippo[i]     int                                      zippo[i][0]
zippo[i][j]   int
&zippo[i][j]  int *
*zippo[i][j]  invalid

Beachten Sie, dass zippo, &zippo, *zippo, zippo[0], &zippo[0]und &zippo[0][0] alle haben den gleichen Wert; sie zeigen alle auf die Basis des Arrays (die Adresse des Arrays ist dieselbe wie die Adresse des ersten Elements des Arrays). Die Typen der verschiedenen Ausdrücke unterscheiden sich jedoch alle.

  • Das hilft mir wirklich zu verstehen, auf wie viele Arten ein Array oder Zeiger dargestellt werden kann und was es bedeutet, wenn eine Anweisung zerfällt. Sehr ausführlich. Vielen Dank.

    – Ichimonji10

    5. Januar 2010 um 23:10 Uhr

Wenn Sie ein mehrdimensionales Array deklarieren, behandelt der Compiler es als eindimensionales Array. Mehrdimensionale Arrays sind nur eine Abstraktion, um uns das Leben zu erleichtern. Sie haben ein Missverständnis: Dies ist nicht ein Array, das auf 4 Arrays zeigt, sondern immer nur ein einziger zusammenhängender Speicherblock.

In Ihrem Fall tun Sie Folgendes:

int zippo[4][2]

Ist wirklich das gleiche wie tun

int zippo[8]

Mit der für die 2D-Adressierung erforderlichen Mathematik, die der Compiler für Sie erledigt.

Einzelheiten finden Sie hier Tutorial zu Arrays in C++.

Dies ist ganz anders als Folgendes zu tun:

int** zippo

oder

int* zippo[4]

In diesem Fall erstellen Sie ein Array aus vier Zeigern, die anderen Arrays zugewiesen werden könnten.

  • Ahhh, das ist eine sehr klare Erklärung. Vielen Dank. Ich werde etwas über den Unterschied zwischen einem einfachen mehrdimensionalen Array (das ein einzelner zusammenhängender Speicherblock ist) und einem Array von Zeigern lesen.

    – Ichimonji10

    5. Januar 2010 um 3:09 Uhr

Benutzer-Avatar
Alok Singhal

zippo ist kein Zeiger. Es ist ein Array von Array-Werten. zippound zippo[i] zum i in 0..4 kann in bestimmten Fällen (insbesondere in Wertekontexten) zu einem Zeiger “zerfallen”. Versuchen Sie zu drucken sizeof zippo für ein Beispiel für die Verwendung von zippo in einem Nicht-Wert-Kontext. In diesem Fall, sizeof meldet die Größe des Arrays, nicht die Größe eines Zeigers.

Der Name eines Arrays, in Wertekontexten, zerfällt zu einem Zeiger auf sein erstes Element. Also, im Wertekontext, zippo ist das gleiche wie &zippo[0]und hat somit den Typ “Zeiger auf ein Array [2] von int“; *zippoim Wertkontext ist dasselbe wie &zippo[0][0]dh “Zeiger auf int“. Sie haben den gleichen Wert, aber unterschiedliche Typen.

Ich empfehle zu lesen Arrays und Zeiger für die Beantwortung Ihrer zweiten Frage. Die Pointer haben den gleichen “Wert”, zeigen aber auf unterschiedlich viel Platz. Versuchen Sie zu drucken zippo+1 und *zippo+1 um das deutlicher zu sehen:

#include <stdio.h>

int main(void)
{
    int zippo[4][2] = { {2,4}, {6,8}, {1,3}, {5,7} };
    printf("%lu\n", (unsigned long) (sizeof zippo));
    printf("%p\n", (void *)(zippo+1));
    printf("%p\n", (void *)(*zippo+1));
    return 0;
}

Für meinen Lauf druckt es:

32
0xbffede7c
0xbffede78

Sag mir das sizeof(int) auf meiner Maschine 4 ist und dass der zweite und der dritte Zeiger nicht den gleichen Wert haben (wie erwartet).

Ebenfalls, "%p" Anforderungen an Formatbezeichner void * in *printf() Funktionen, also sollten Sie Ihre Zeiger darauf werfen void * in deiner printf() Anrufe (printf() ist eine variadische Funktion, daher kann der Compiler die automatische Konvertierung hier nicht für Sie übernehmen).

Bearbeiten: Wenn ich sage, dass ein Array zu einem Zeiger “zerfällt”, meine ich, dass der Name eines Arrays im Wertkontext einem Zeiger entspricht. Also, wenn ich habe T pt[100]; für irgendeinen Typ Tdann der Name pt ist vom Typ T * in Wertekontexten. Zum sizeof und unär & Betreiber, der Name pt reduziert sich nicht auf einen Zeiger. Aber du kannst es tun T *p = pt;– dies ist vollkommen gültig, denn in diesem Zusammenhang pt ist vom Typ T *.

Beachten Sie, dass dieses „Zerfallen“ nur einmal vorkommt. Nehmen wir also an, wir haben:

int zippo[4][2] = { {2,4}, {6,8}, {1,3}, {5,7} };

Dann, zippo im Wertkontext zerfällt zu einem Zeiger vom Typ: Zeiger auf Array[2] von int. In Code:

int (*p1)[2] = zippo;

gilt, während

int **p2 = zippo;

löst eine Warnung “inkompatible Zeigerzuweisung” aus.

Mit zippo wie oben definiert,

int (*p0)[4][2] = &zippo;
int (*p1)[2] = zippo;
int *p2 = zippo[0];

sind alle gültig. Sie sollten denselben Wert drucken, wenn sie mit gedruckt werden printf("%p\n", (void *)name);aber die Zeiger unterscheiden sich darin, dass sie auf die gesamte Matrix, eine Zeile bzw. eine einzelne Ganzzahl zeigen.

  • Mal sehen, ob ich damit richtig liege: Obwohl zippo und zippo[0] zeigen auf unterschiedliche Speichermengen, ihre Startadresse ist dieselbe, und in Wertekontexten Beide zeigen auf die erste Ganzzahl in Zippo. Richtig? Und meinst du das mit “Verfall”?

    – Ichimonji10

    5. Januar 2010 um 3:23 Uhr

  • Nein. zippo[0] in Wertekontexten ist int *und zeigt auf die erste Ganzzahl, wo zippo in Wertekontexten ist “Zeiger auf ein Array [2] von int“. Du könntest es tun: int (*p)[2]; p = zippo; zum Beispiel. Dann, p[0] == 2 und p[1] == 4. Für “Zerfall” siehe meine (bald erscheinende) Bearbeitung.

    – Alok Singhal

    5. Januar 2010 um 4:51 Uhr


Benutzer-Avatar
dmckee — Ex-Moderator-Kätzchen

Das Wichtige hier ist das int zippy[4][2] ist nicht derselbe Objekttyp wie int **zippo.

So wie int zippi[5], zippy ist die Adresse eines Speicherblocks. Aber der Compiler weiß, dass Sie die acht Speicherplätze ab adressieren wollen zippy mit einer zweidimensionalen Syntax, wollen aber die fünf Speicherplätze ab adressieren zippi mit eindimensionaler Syntax.

zippo ist eine ganz andere Sache. Es enthält die Adresse eines Speicherblocks, der groß genug ist, um zwei Zeiger zu enthalten, und wenn Sie machen Sie auf einige Arrays von Ganzzahlen zeigen, können Sie sie mit der zweidimensionalen Array-Zugriffssyntax dereferenzieren.

Benutzer-Avatar
Abhijit K Rao

Sehr gut erklärt von Reed, ich werde einige weitere Punkte hinzufügen, um es einfacher zu machen, wenn wir darauf verweisen zippo oder zippo[0] oder zippo[0][0]beziehen wir uns immer noch auf dieselbe Basisadresse des Arrays zippo. Der Grund dafür ist, dass Arrays immer zusammenhängende Speicherblöcke sind und mehrdimensionale Arrays mehrere eindimensionale Arrays sind, die kontinuierlich platziert werden.

Wenn Sie um jede Zeile inkrementieren müssen, benötigen Sie einen Zeiger int *p = &zippo[0][0]und tun p++ erhöht den Zeiger um jede Zeile. In Ihrem Beispiel handelt es sich um ein 4 x 2-Array p++ Sein Zeiger zeigt derzeit auf den zweiten Satz von 4 Elementen.

1372660cookie-checkZeigeradresse in einem mehrdimensionalen C-Array

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

Privacy policy