it-swarm.com.de

Eine ungewöhnliche Art, zweidimensionale Arrays zuzuordnen?

In einem Projekt drückte jemand diese Linie:

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

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

Angeblich, ich sage, weil bisher niemand, den ich gefragt habe, mir sagen konnte, was dies genau tut, woher es kommt oder warum es funktionieren sollte (was angeblich funktioniert, aber ich bin es nicht noch kaufen).

Vielleicht fehlt mir etwas Offensichtliches, aber ich würde es begrüßen, wenn mir jemand das oben Gesagte erklären könnte. Denn persönlich würde ich mich viel besser fühlen, wenn wir etwas verwenden würden, das wir tatsächlich verstehen.

110
User1291

Die Variable e ist ein Zeiger auf ein Array von n + 1 - Elementen vom Typ double.

Wenn Sie den Dereferenzierungsoperator für e verwenden, erhalten Sie den Basistyp von e, bei dem es sich um ein "Array von n + 1 - Elementen vom Typ double" handelt.

Der malloc -Aufruf nimmt einfach den Basistyp von e (oben erläutert) und erhält seine Größe, multipliziert ihn mit n + 1 Und übergibt diese Größe an malloc Funktion. Zuweisen eines Arrays von n + 1 Arrays von n + 1 Elementen von double.

87

Dies ist die typische Methode, mit der Sie 2D-Arrays dynamisch zuweisen sollten.

  • e ist ein Arrayzeiger auf ein Array vom Typ double [n+1].
  • sizeof(*e) gibt daher den Typ des angezeigten Typs an, der die Größe eines Arrays double [n+1] hat.
  • Sie vergeben Platz für n+1 Solche Arrays.
  • Sie setzen den Array-Zeiger e so, dass er auf das erste Array in diesem Array von Arrays zeigt.
  • Auf diese Weise können Sie e als e[i][j] Verwenden, um auf einzelne Elemente im 2D-Array zuzugreifen.

Persönlich denke ich, dass dieser Stil viel einfacher zu lesen ist:

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

Diese Redewendung fällt natürlich aus der 1D-Array-Zuordnung heraus. Beginnen wir mit der Zuweisung eines 1D-Arrays eines beliebigen Typs T:

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

Einfach, richtig? Der Ausdruck*p Hat den Typ T, also liefert sizeof *p Das gleiche Ergebnis wie sizeof (T), wir ordnen also genug zu Platz für ein N- Elementarray von T. Dies gilt für jeder Typ T.

Ersetzen wir nun T durch einen Array-Typ wie R [10]. Dann wird unsere Zuordnung

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

Die Semantik hier ist genau dasselbe wie die 1D-Zuweisungsmethode; alles, was sich geändert hat, ist der Typ von p. Anstelle von T * Ist es jetzt R (*)[10]. Der Ausdruck *p Hat den Typ T, der dem Typ R [10] Entspricht, also ist sizeof *p Gleichbedeutend mit sizeof (T), der gleichbedeutend mit sizeof (R [10]). Wir reservieren also genug Platz für ein N mit dem Elementarray 10 Von R.

Wir können das noch weiter führen, wenn wir wollen; Angenommen, R ist selbst ein Array-Typ int [5]. Ersetzen Sie das für R und wir bekommen

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

Gleicher Deal - sizeof *p Ist derselbe wie sizeof (int [10][5]), und wir ordnen einen zusammenhängenden Speicherblock zu, der groß genug ist, um ein N von 10 Von zu halten Array 5 Von int.

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

Denken Sie daran, dass die tiefgestellte Operation []definiert in Bezug auf Zeigerarithmetik ist: a[i] Ist definiert als *(a + i)1. Der tiefgestellte Operator []implizit dereferenziert also einen Zeiger. Wenn p ein Zeiger auf T ist, können Sie entweder durch explizites Dereferenzieren mit dem unären Operator * Auf den Wert zugreifen, auf den verwiesen wird:

T x = *p;

oder mit dem tiefgestellten Operator []:

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

Wenn also p auf das erste Element eines Arrays verweist, können Sie auf jedes Element dieses Arrays zugreifen, indem Sie einen Index für den Zeiger p verwenden:

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 Ersetzungsoperation wiederholen und T durch den Array-Typ R [10] Ersetzen:

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 sichtbarer Unterschied; Wir dereferenzieren p explizit, bevor wir den Indexoperator anwenden. Wir wollen nicht in p subskribieren, wir wollen in das subskribieren, was pzeigt auf (in diesem Fall das Arrayarr[0]). Da unary * Eine niedrigere Priorität als der tiefgestellte Operator [] Hat, müssen wir Klammern verwenden, um p explizit mit * Zu gruppieren. Aber denken Sie von oben daran, dass *p Dasselbe ist wie p[0], Also können wir das durch ersetzen

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

oder nur

R x = p[0][i];

Wenn also p auf ein 2D-Array zeigt, können wir dieses Array durch p wie folgt indizieren:

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

Gehen Sie wie oben vor und ersetzen Sie R durch 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];

Dies funktioniert genauso wenn p auf ein reguläres Array zeigt oder wenn es auf Speicher zeigt, der durch malloc zugewiesen wurde.

Diese Redewendung hat folgende Vorteile:

  1. Es ist ganz einfach - nur eine Codezeile im Gegensatz zur Methode der schrittweisen Zuweisung
    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 oben beschriebenen Methode zur schrittweisen Zuordnung nicht der Fall ist.
  3. Das Aufheben der Zuordnung des Arrays ist mit einem einzigen Aufruf von free genauso einfach. Auch dies gilt nicht für die Methode der stückweisen Zuordnung, bei der Sie die Zuordnung von arr[i] Aufheben müssen, bevor Sie die Zuordnung von arr aufheben können.

Manchmal ist die schrittweise Zuweisung vorzuziehen, beispielsweise wenn Ihr Heap stark fragmentiert ist und Sie Ihren Speicher nicht als zusammenhängenden Block zuordnen können, oder wenn Sie ein "gezacktes" Array zuweisen möchten, bei dem jede Zeile eine andere Länge haben kann. Aber im Allgemeinen ist dies der bessere Weg.


1. Denken Sie daran, dass Arrays nicht Zeiger - stattdessen werden Arrays Ausdrücke nach Bedarf in Zeigerausdrücke konvertiert.
39
John Bode