Wie richte ich ein mehrdimensionales Array in C richtig ein, greife darauf zu und befreie es?

Lesezeit: 9 Minuten

Ich habe Dutzende von Fragen zu „Was ist mit meinem Code falsch?“ in Bezug auf mehrdimensionale Arrays in C gesehen. Aus irgendeinem Grund können die Leute nicht verstehen, was hier passiert, also habe ich beschlossen, diese Frage als Referenz für andere zu beantworten :

Wie richte ich ein mehrdimensionales Array in C richtig ein, greife darauf zu und befreie es?

Wenn andere hilfreiche Tipps haben, können Sie diese gerne mit posten!

In C seit C99 können sogar dynamische mehrdimensionale Arrays einfach auf einmal zugewiesen werden malloc und befreit mit free:

double (*A)[n] = malloc(sizeof(double[n][n]));

for (size_t i = 0; i < n; ++i)
  for (size_t j = 0; j < n; ++j)
      A[i][j] = someinvolvedfunction(i, j);

free(A);

  • Dies ist der bevorzugte Weg, vermeiden Sie Zeiger-zu-Zeiger-Syntax. Ich bin mir nicht sicher, aber ich glaube, das hat auch in C90 funktioniert, aber? Sicherlich gab es Array-Zeiger schon vor C99? Zumindest “verstümmelte” Arrays funktionierten, dh double* A = malloc(x*y*sizeof(double));.

    – Ludin

    17. September 2012 um 15:54 Uhr

  • @Lundin, nein leider der Erklärungsteil double (*A)[n] funktionierte nur wenn n war eine Kompilierzeitkonstante, im Grunde ein Makro oder enum Konstante.

    – Jens Gustedt

    17. September 2012 um 15:56 Uhr

  • Aha, nun, ich denke, es macht nicht viel Sinn, dynamisch mit der Größe zuzuweisen, die zur Kompilierzeit bekannt ist 🙂 Obwohl, ist das ‘n’ obligatorisch? Könntest du nicht schreiben double (*A)[] = ?

    – Ludin

    17. September 2012 um 16:02 Uhr

  • @Lundin: Manchmal ist es sinnvoll, dynamisch mit der zur Kompilierzeit bekannten Größe zuzuweisen, da ein mehrdimensionales Array den Stapel ziemlich leicht sprengen kann.

    – Steve Jessop

    17. September 2012 um 16:05 Uhr

  • @JensGustedt Können Sie A von einer Funktion zurückgeben, und wenn ja, was ist der Rückgabetyp?

    – Roller

    21. September 2012 um 16:43 Uhr

Benutzer-Avatar
Steve Jessop

Es gibt mindestens vier verschiedene Möglichkeiten, ein mehrdimensionales Array in C89 zu erstellen oder zu simulieren.

Einer ist “jede Zeile separat zuweisen”, beschrieben von Mike in seiner Antwort. es ist nicht B. ein mehrdimensionales Array, imitiert es lediglich eines (insbesondere ahmt es die Syntax für den Zugriff auf ein Element nach). Dies kann nützlich sein, wenn jede Zeile eine andere Größe hat, sodass Sie keine Matrix darstellen, sondern etwas mit einer “ausgefransten Kante”.

Einer ist “ein mehrdimensionales Array zuweisen”. Es sieht so aus:

int (*rows)[NUM_ROWS][NUM_COLS] = malloc(sizeof *rows);
...
free(rows);

Dann die Syntax zum Zugriffselement [i,j] ist (*rows)[i][j]. In C89 beides NUM_COLS und NUM_ROWS muss zur Kompilierzeit bekannt sein. Dies ist ein echtes 2-D-Array, und rows ist ein Hinweis darauf.

Eines ist “ein Array von Zeilen zuweisen”. Es sieht aus wie das:

int (*rows)[NUM_COLS] = malloc(sizeof(*rows) * NUM_ROWS);
...
free(rows);

Dann die Syntax zum Zugriffselement [i,j] ist rows[i][j]. In C89, NUM_COLS muss zur Kompilierzeit bekannt sein. Dies ist ein echtes 2-D-Array.

Eines ist “ein 1-d-Array zuweisen und so tun”. Es sieht aus wie das:

int *matrix = malloc(sizeof(int) * NUM_COLS * NUM_ROWS);
...
free(matrix);

Dann die Syntax zum Zugriffselement [i,j] ist matrix[NUM_COLS * i + j]. Dies ist (natürlich) kein echtes 2-D-Array. In der Praxis hat es das gleiche Layout wie eins.

  • “Ordnen Sie ein Array von Zeilen zu”, ist dies nicht eher: Weisen Sie ein Array von Arrays zu und weisen Sie dann einen Array-Zeiger zu, der auf das erste Objekt / Array zeigt? Ich selbst verwende immer diese Form, aber vielleicht ist der “2D”-Zeiger stilistisch richtiger?

    – Ludin

    17. September 2012 um 16:00 Uhr

  • @Lundin: es ist beides. In allen Formen (außer wohl dem abgeflachten Array) ist jede Zeile ein Array, also ist ein Array von Zeilen ein Array von Arrays. Aber da ein mehrdimensionales Array ist sowieso ein Array von Arrays (per Definition im Standard), meine Titel unterscheiden technisch gesehen nicht zwischen ihnen. Für mich ist der Unterschied in der Betonung klar, für andere vielleicht nicht.

    – Steve Jessop

    17. September 2012 um 16:02 Uhr


  • Nachdem ich darüber nachgedacht habe, würde ich definitiv sagen, dass die erste Version zu bevorzugen ist, da sie einem Compiler oder einem statischen Analysetool die Möglichkeit geben würde, eine “stärkere Typisierung” durchzusetzen, indem falsche, implizite Typkonvertierungen erkannt und davor gewarnt werden. Die 2. und 3. Form könnten versehentlich mit einfachen 1D-Arrays oder einfachen Zeigern verwechselt werden, ohne dass Tools die Möglichkeit hätten, mögliche Fehler zu erkennen.

    – Ludin

    17. September 2012 um 16:37 Uhr

  • Keine Respektlosigkeit gegenüber Ihrer Analyse, die meiner Meinung nach wahrscheinlich richtig ist, aber wenn ich etwas bevorzuge, sage ich einfach, dass ich es bevorzuge, und versuche, mich daran zu erinnern, es nicht “bevorzugt” zu sagen. Meine Bedenken sind möglicherweise nicht die gleichen wie die von jemand anderem, und insbesondere in C89 ist die Notwendigkeit von Grenzen, die zur Kompilierzeit bekannt sind, ziemlich einschränkend. Die Syntax für die erste Option ist nicht so einladend, aber sie ermöglicht sicherlich eine statische Begrenzungsprüfung durch den Compiler in beiden Dimensionen und nicht nur in einer.

    – Steve Jessop

    17. September 2012 um 16:44 Uhr


  • @mk..: der erste.

    – Steve Jessop

    14. Oktober 2015 um 7:31 Uhr

Benutzer-Avatar
Mike

Statisch gesehendas ist leicht zu verstehen:

int mtx[3][2] = {{1, 2},
                 {2, 3},
                 {3, 4}};

Hier ist nichts kompliziert. 3 Zeilen, 2 Spalten; Daten in Spalte eins: 1, 2, 3; Daten in Spalte zwei: 2, 3, 4. Wir können auf die Elemente über dasselbe Konstrukt zugreifen:

for(i = 0; i<3; i++){
    for(j = 0; j<2; j++)
        printf("%d ", mtx[i][j]);
    printf("\n");
}
//output
//1 2
//2 3
//3 4

Betrachten wir dies nun im Hinblick auf Zeiger:

Die Klammern sind ein sehr nettes Konstrukt, um die Dinge zu vereinfachen, aber es hilft nicht, wenn wir in einer dynamischen Umgebung arbeiten müssen, also müssen wir uns das als Zeiger vorstellen. Wenn wir eine „Zeile“ von ganzen Zahlen speichern wollen, brauchen wir ein Array:

int row[2] = {1,2};

Und weisst du was? Darauf können wir wie auf einen Zeiger zugreifen.

printf("%d, %d\n",*row,*(row+1));   //prints 1, 2
printf("%d, %d\n",row[0],row[1]);   //prints 1, 2

Wenn wir jetzt die Anzahl der Werte in einer Reihe nicht kennen, können wir diesem Array eine dynamische Länge geben, wenn wir einen Zeiger auf int haben, und wir geben ihm etwas Speicher:

int *row = malloc(X * sizeof(int));  //allow for X number of ints
*row = 1;        //row[0] = 1
*(row+1) = 2; //row[1] = 2
…
*(row+(X-1)) = Y; // row[x-1] = Some value y

Jetzt haben wir also ein dynamisches 1-dimensionales Array; eine einzelne Reihe. Aber wir wollen viele Zeilen, nicht nur eine, und wir wissen nicht, wie viele. Das heißt, wir brauchen ein weiteres dynamisches 1-dimensionales Array, jedes Element dieses Arrays ist ein Zeiger, der auf eine Zeile zeigt.

//we want enough memory to point to X number of rows
//each value stored there is a pointer to an integer
int ** matrix = malloc(X * sizeof(int *));

//conceptually:
(ptr to ptr to int)     (pointer to int)
   **matrix ------------> *row1 --------> [1][2]
                          *row2 --------> [2][3]
                          *row3 --------> [3][4]

Jetzt müssen Sie nur noch den Code schreiben, der diese dynamischen Zuordnungen durchführt:

int i, j, value = 0;

//allocate memory for the pointers to rows
int ** matrix = malloc(Rows * sizeof(int*));

//each row needs a dynamic number of elements
for(i=0; i<Rows; i++){
    // so we need memory for the number of items in each row… 
    // we could call this number of columns as well
    *(matrix + i) = malloc(X * sizeof(int));

     //While we’re in here, if we have the items we can populate the matrix
    for(j=0; j<X; j++)
        *(*(matrix+i)+j) = value; // if you deference (matrix + i) you get the row
                                  // if you add the column and deference again, you
                                  // get the actual item to store (not a pointer!)
}

Eines der wichtigsten Dinge, die Sie jetzt tun müssen, ist sicherzustellen, dass wir den Speicher freigeben, wenn wir fertig sind. Jede Ebene von malloc() sollte die gleiche Anzahl haben free() Aufrufe, und die Aufrufe sollten in einer FILO-Reihenfolge erfolgen (Umkehrung der malloc-Aufrufe):

for(i=0; i<Rows; i++) 
    free(*(matrix + i));
free(matrix);

//set to NULL to clean up, matrix points to allocated memory now so let’s not use it!
matrix = NULL; 

  • Gute Antwort, aber bitte verwenden Sie nicht die Zeiger-zu-Zeiger-Syntax, sie erstellt segmentiertes Multi-Dim. Arrays, die weder mit statisch zugewiesenen Arrays noch mit C-Standardbibliotheksfunktionen wie memcpy, memset, bsearch, qsort usw. kompatibel sind. Siehe Jens Antwort für die bevorzugte Methode zum Zuweisen von dynamischem Multi-Dim. Arrays.

    – Ludin

    17. September 2012 um 15:51 Uhr

  • @Lundin – Ein großartiger Punkt, ich habe mich für die Verwendung der Zeiger-zu-Zeiger-Syntax entschieden, da sie mir damals so beigebracht wurde und ich denke, dass sie immer noch so gelehrt wird (basierend auf den Fragen, die ich auf SO gesehen habe).

    – Mike

    17. September 2012 um 16:03 Uhr

  • Es ist keine „Syntax“. Syntax sind Regeln über die Sprache oder umgangssprachlich ein bestimmtes Sprachmuster. Fragen der Syntax sind Fragen des Ausdrucks und der Kommunikation. Das Problem mit der Zeiger-zu-Zeiger-Methode ist nicht nur die verwendete Sprache, sondern die verschwenderischen Aktionen, die sie im Programm verursacht: Es wird mehr Speicher als nötig verwendet (für die nicht benötigten Zeiger und für die zusätzliche Abrechnung, wenn jede Zeile separat zugewiesen wird). , wird mehr Zeit als nötig verwendet (Laden eines Zeigers bei jedem Zugriff auf eine Zeile und zusätzliche Zuordnungsaufrufe), und der Code ist komplexer als nötig.

    – Eric Postpischil

    17. September 2012 um 16:15 Uhr

  • @EricPostpischil Es ist Syntax, weil der verwendete Typ ist int** statt int (*)[].

    – Ludin

    17. September 2012 um 16:17 Uhr

  • @Lundin: Das ist so, als würde man sagen, der Unterschied zwischen Paris und einer thermonuklearen Bombe sei die Schreibweise, weil das eine „Paris“ und das andere „thermonukleare Bombe“ geschrieben wird. Tatsächlich ist es nicht die Syntax, die den Kernunterschied oder den Unterschied mit der größten Wirkung darstellt. Die Syntax ist nur ein Kommunikationsmittel; es ist das, was kommuniziert wird, das eigentliche Problem. Eine andere Möglichkeit, dies zu sehen, besteht darin, es in eine andere Sprache zu übersetzen: Angenommen, die Syntax wurde vertauscht, aber das zugrunde liegende Verhalten bleibt gleich. Wäre das besser? Nein, das Doppelzeigerproblem würde bleiben.

    – Eric Postpischil

    17. September 2012 um 16:31 Uhr

Wenn Sie ein typdefiniertes Array verwenden möchten, ist es noch einfacher.

Sagen Sie, Sie haben in Ihrem Code typedef int LabeledAdjMatrix[SIZE][SIZE];

Sie können dann verwenden:

LabeledAdjMatrix *pMatrix = malloc(sizeof(LabeledAdjMatrix));

Dann kannst du schreiben:

for (i=0; i<SIZE; i++) {
    for (j=0; j<SIZE; j++) (*parr)[i][j] = k++; /* or parr[0][i][j]... */
}

Da pArr ist ein Zeiger auf Ihre Matrix und * hat eine niedrigere Priorität als [];

Aus diesem Grund ist eine allgemeine Redewendung für den Modus die Typdefinition der Zeile:

typedef int LabeledAdjRow[SIZE];

Dann kannst du schreiben:

LabeledAdjRow *pMatrix = malloc(sizeof(LabeledAdjRow) * SIZE);
for (i=0; i<SIZE; i++) {
    for (j=0; j<SIZE; j++) parr[i][j] = k++;
}

Und du kannst memcpy das alles direkt:

LabeledAdjRow *pOther = malloc(sizeof(LabeledAdjRow) * SIZE);
memcpy(pOther, pMatrix, sizeof(LabeledAdjRow) * SIZE);

Wenn Sie von Jens Antwort ausgehen und Platz für ein 3D-Array zuweisen möchten, können Sie dies auf die gleiche Weise tun

int(*A)[n][n] = malloc(sizeof(int[num_of_2D_arrays][n][n]));

for (size_t p = 0; p < num_of_2D_arrays; p++)
  for (size_t i = 0; i < n; i++)
    for (size_t j = 0; j < n; j++)
      A[p][i][j] = p;

for (size_t p = 0; p < num_of_2D_arrays; p++)
    printf("Outter set %lu\n", p);
    for (size_t i = 0; i < n; i++)
      for (size_t j = 0; j < n; j++)
        printf(" %d", A[p][i][j]);
      printf("\n");

free(A);

1342500cookie-checkWie richte ich ein mehrdimensionales Array in C richtig ein, greife darauf zu und befreie es?

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

Privacy policy