Freakige Art, ein zweidimensionales Array zuzuweisen?

Lesezeit: 8 Minuten

Benutzeravatar von User1291
Benutzer1291

In einem Projekt hat jemand diese Zeile verschoben:

double (*e)[n+1] = malloc((n+1) * sizeof(*e));

Was angeblich ein zweidimensionales Array von (n + 1) * (n + 1) Doubles erzeugt.

Angeblichsage ich, weil mir bisher niemand, den ich gefragt habe, sagen konnte, was das genau macht, woher es kommt oder warum es funktionieren sollte (was es angeblich tut, aber ich kaufe es noch nicht).

Vielleicht übersehe ich etwas Offensichtliches, aber ich würde es begrüßen, wenn mir jemand die obige Zeile erklären könnte. Denn ich persönlich würde mich viel besser fühlen, wenn wir etwas verwenden würden, das wir wirklich verstehen.

  • Fürs Protokoll, das ist die einzige Möglichkeit, ein tatsächliches 2D-Array dynamisch zuzuweisen.

    – QUentin

    22. April 2016 um 13:06 Uhr

Die Variable e ist ein Zeiger auf ein Array von n + 1 Elemente des Typs double.

Verwenden des Dereferenzierungsoperators on e gibt Ihnen den Basistyp von e das ist “Array von n + 1 Elemente des Typs double“.

Das malloc Aufruf nimmt einfach den Basistyp von e (oben erklärt) und erhält seine Größe, multipliziert sie mit n + 1und diese Größe an die übergeben malloc Funktion. Im Wesentlichen Zuweisung eines Arrays von n + 1 Arrays von n + 1 Elemente von double.

  • @MartinJames sizeof(*e) ist äquivalent zu sizeof(double [n + 1]). Multiplizieren Sie das mit n + 1 und du bekommst genug.

    – Irgendein Programmierer-Typ

    22. April 2016 um 12:58 Uhr

  • @MartinJames: Was ist daran falsch? Es ist nicht so auffällig, es garantiert, dass zugewiesene Zeilen zusammenhängend sind, und Sie können es wie jedes andere 2D-Array indizieren. Ich verwende diese Redewendung häufig in meinem eigenen Code.

    – Johannes Bode

    22. April 2016 um 13:11 Uhr

  • Es mag offensichtlich erscheinen, aber das funktioniert nur für Quadrat Arrays (gleiche Dimensionen).

    – Jens

    22. April 2016 um 17:09 Uhr

  • @Jens: Nur in dem Sinne, dass wenn Sie setzen n+1 für beide Dimensionen ist das Ergebnis quadratisch. Wenn Sie tun double (*e)[cols] = malloc(rows * sizeof(*e));hat das Ergebnis die von Ihnen angegebene Anzahl von Zeilen und Spalten.

    – Benutzer2357112

    22. April 2016 um 21:47 Uhr

  • @ user2357112 Nun, das würde ich viel lieber sehen. Auch wenn es bedeutet, dass Sie hinzufügen müssen int rows = n+1 und int cols = n+1. Gott bewahre uns vor schlauem Code.

    – kandiert_orange

    22. April 2016 um 23:19 Uhr

Benutzeravatar von Lundin
Lundin

Dies ist die typische Art und Weise, wie Sie 2D-Arrays dynamisch zuweisen sollten.

  • e ist ein Array-Zeiger auf ein Array vom Typ double [n+1].
  • sizeof(*e) gibt daher den Typ des spitz zulaufenden Typs an, der die Größe von eins hat double [n+1] Reihe.
  • Sie weisen Raum zu n+1 solche Arrays.
  • Sie setzen den Array-Zeiger e um auf das erste Array in diesem Array von Arrays zu zeigen.
  • Dies ermöglicht Ihnen die Verwendung e wie e[i][j] um auf einzelne Elemente im 2D-Array zuzugreifen.

Ich persönlich denke, dass dieser Stil viel einfacher zu lesen ist:

double (*e)[n+1] = malloc( sizeof(double[n+1][n+1]) );

  • Gute Antwort, außer dass ich mit Ihrem vorgeschlagenen Stil nicht einverstanden bin und den bevorzuge ptr = malloc(sizeof *ptr * count) Stil.

    – chux – Wiedereinsetzung von Monica

    22. April 2016 um 13:35 Uhr

  • Gute Antwort, und ich mag deinen bevorzugten Stil. Eine kleine Verbesserung könnte darin bestehen, darauf hinzuweisen, dass Sie dies auf diese Weise tun müssen, da möglicherweise eine Auffüllung zwischen den Zeilen vorhanden ist, die berücksichtigt werden muss. (Zumindest denke ich, dass das der Grund ist, warum Sie es so machen müssen.) (Lassen Sie mich wissen, wenn ich falsch liege.)

    – davidbak

    22. April 2016 um 17:27 Uhr

  • @davidbak Das ist dasselbe. Die Array-Syntax ist lediglich selbstdokumentierender Code: Sie sagt “Raum für ein 2D-Array zuweisen” mit dem Quellcode selbst.

    – Ludin

    22. April 2016 um 18:52 Uhr

  • @davidbak Hinweis: Ein kleiner Nachteil des Kommentars malloc(row*col*sizeof(double)) passiert wenn row*col*sizeof() überläuft, aber nicht sizeof()*row*col nicht. (zB Zeile,Spalte sind int)

    – chux – Wiedereinsetzung von Monica

    22. April 2016 um 19:51 Uhr


  • @davidbak: sizeof *e * (n+1) ist einfacher zu pflegen; Wenn Sie sich jemals entscheiden, den Basistyp zu ändern (von double zu long doublezum Beispiel), dann müssen Sie nur die Deklaration von ändern e; Sie müssen die nicht ändern sizeof Ausdruck in der malloc anrufen (was Zeit spart und Sie davor schützt, es an einer Stelle zu ändern, aber nicht an der anderen). sizeof *e gibt Ihnen immer die richtige Größe.

    – Johannes Bode

    22. April 2016 um 23:48 Uhr

Benutzeravatar von John Bode
Johannes Bode

Dieses Idiom fällt natürlich aus der 1D-Array-Zuweisung. Beginnen wir mit der Zuweisung eines 1D-Arrays eines beliebigen Typs T:

T *p = malloc( sizeof *p * N );

Einfach, oder? Das Ausdruck *p Typ hat TAlso sizeof *p liefert das gleiche Ergebnis wie sizeof (T)also weisen wir genügend Platz für eine zu N-Elementarray von T. Dies gilt für jeder Typ T.

Lassen Sie uns nun ersetzen T mit einem Array-Typ wie R [10]. Dann wird unsere Zuordnung

R (*p)[10] = malloc( sizeof *p * N);

Die Semantik hier ist genauso als 1D-Zuordnungsverfahren; alles, was sich geändert hat, ist die Art von p. Anstatt von T *es schneit R (*)[10]. Der Ausdruck *p Typ hat T was Typ ist R [10]Also sizeof *p ist äquivalent zu sizeof (T) was äquivalent ist sizeof (R [10]). Also weisen wir genug Platz für eine zu N durch 10 Elementarray von R.

Wir können das sogar noch weiter treiben, wenn wir wollen; vermuten R ist selbst ein Array-Typ int [5]. Ersetze das durch R und wir bekommen

int (*p)[10][5] = malloc( sizeof *p * N);

Gleicher Deal – sizeof *p ist das gleiche wie sizeof (int [10][5])und am Ende weisen wir einen zusammenhängenden Teil des Speichers zu, der groß genug ist, um a aufzunehmen N durch 10 durch 5 Anordnung von int.

Das ist also die Allokationsseite; Was ist mit der Zugangsseite?

Denken Sie daran, dass die [] Indexoperation ist definiert in Bezug auf die Zeigerarithmetik: a[i] ist definiert als *(a + i)1. Also der Indexoperator [] implizit dereferenziert einen Zeiger. Wenn p ist ein Hinweis auf Tkönnen Sie auf den Wert, auf den gezeigt wird, zugreifen, indem Sie entweder explizit mit dem unären dereferenzieren * Operator:

T x = *p;

oder durch die Verwendung der [] Indexoperator:

T x = p[0]; // identical to *p

Also wenn p zeigt auf das erste Element von an Reihekönnen Sie auf jedes Element dieses Arrays zugreifen, indem Sie einen Index auf dem Zeiger verwenden p:

T arr[N];
T *p = arr; // expression arr "decays" from type T [N] to T *
...
T x = p[i]; // access the i'th element of arr through pointer p

Lassen Sie uns nun unsere Substitutionsoperation wiederholen und ersetzen T mit dem Array-Typ R [10]:

R arr[N][10];
R (*p)[10] = arr; // expression arr "decays" from type R [N][10] to R (*)[10]
...
R x = (*p)[i];

Ein sofort offensichtlicher Unterschied; wir dereferenzieren explizit p bevor der Indexoperator angewendet wird. Wir wollen nicht subskribieren pwir wollen was subskribieren p verweist auf (in diesem Fall die Reihe arr[0]). Da unär * hat einen niedrigeren Vorrang als der Index [] -Operator müssen wir Klammern verwenden, um explizit zu gruppieren p mit *. Aber denken Sie daran von oben *p ist das gleiche wie p[0]also können wir das durch ersetzen

R x = (p[0])[i];

oder nur

R x = p[0][i];

Also wenn p auf ein 2D-Array zeigt, können wir durch dieses Array indizieren p so:

R x = p[i][j]; // access the i'th element of arr through pointer p;
               // each arr[i] is a 10-element array of R

Nehmen Sie dies zu der gleichen Schlussfolgerung wie oben und ersetzen Sie es R mit int [5]:

int arr[N][10][5];
int (*p)[10][5]; // expression arr "decays" from type int [N][5][10] to int (*)[10][5]
...
int x = p[i][j][k];

Das funktioniert nur das Gleiche wenn p auf ein reguläres Array zeigt oder auf Speicher zeigt, der durch zugewiesen wurde malloc.

Diese Redewendung hat folgende Vorteile:

  1. Es ist einfach – nur eine Codezeile, im Gegensatz zur stückweisen Zuordnungsmethode
    T **arr = malloc( sizeof *arr * N );
    if ( arr )
    {
      for ( size_t i = 0; i < N; i++ )
      {
        arr[i] = malloc( sizeof *arr[i] * M );
      }
    }
    
  2. Alle Zeilen des zugewiesenen Arrays sind *zusammenhängend*, was bei der obigen Methode der stückweisen Zuweisung nicht der Fall ist;
  3. Das Freigeben des Arrays ist mit einem einzigen Aufruf genauso einfach free. Auch dies gilt nicht für die stückweise Zuweisungsmethode, bei der Sie jede Zuweisung aufheben müssen arr[i] bevor Sie die Zuordnung aufheben können arr.

Manchmal ist die stückweise Zuordnungsmethode vorzuziehen, z. B. wenn Ihr Heap stark fragmentiert ist und Sie Ihren Speicher nicht als zusammenhängenden Block zuweisen können oder wenn Sie ein “gezacktes” Array zuweisen möchten, in dem jede Zeile eine andere Länge haben kann. Aber im Allgemeinen ist dies der bessere Weg.


1. Denken Sie daran, dass Arrays sind nicht Zeiger – stattdessen Array Ausdrücke werden bei Bedarf in Zeigerausdrücke konvertiert.

  • +1 Mir gefällt die Art und Weise, wie Sie das Konzept präsentieren: Das Zuweisen einer Reihe von Elementen ist für jeden Typ möglich, auch wenn diese Elemente selbst Arrays sind.

    – logo_writer

    24. April 2016 um 0:27 Uhr

  • Ihre Erklärung ist wirklich gut, aber beachten Sie, dass die Zuweisung von zusammenhängendem Speicher kein Vorteil ist, bis Sie ihn wirklich brauchen. Kontinuierlicher Speicher ist teurer als nicht zusammenhängender. Bei einfachen 2D-Arrays gibt es für Sie keinen Unterschied im Speicherlayout (mit Ausnahme der Anzahl der Zeilen für die Zuweisung und Freigabe). Verwenden Sie daher lieber nicht zusammenhängenden Speicher.

    – Oleg Lokschin

    27. April 2016 um 8:31 Uhr

  • @John Bode, was ist der beste Weg (wenn es möglich ist), zurückzukehren int (*p)[10][5] = malloc( sizeof *p * N); aus einer Funktion. Aber ich möchte ar bewahren[x][y] Notation.

    – AdR

    11. Dezember 2020 um 21:33 Uhr

  • @CoR: Wenn ich Ihre Frage richtig verstehe, würden Sie einfach zurückkommen p. Der Funktionsprototyp wäre int (*foo(int N))[10][5] (foo ist eine Funktion, die an nimmt int Parameter N und gibt einen Zeiger auf ein 10×5-Array von zurück int).

    – Johannes Bode

    11. Dezember 2020 um 23:30 Uhr

  • @John Bode Das muss ich vermeiden int (*foo(int N))[10][5] Prototyp. 10 und 5 werden später vom Benutzer bereitgestellt. Ist es mit dieser Notation möglich, eine C-Funktion zu erstellen, die ein Array oder einen Zeiger auf ein mallociertes Array oder einen Zeiger auf einen Zeiger “zurückgibt”?

    – AdR

    12. Dezember 2020 um 13:52 Uhr

1422470cookie-checkFreakige Art, ein zweidimensionales Array zuzuweisen?

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

Privacy policy