Forum: Compiler & IDEs Ein Zeiger auf ein mehrdimensionales Feld


von Martin (Gast)


Lesenswert?

Hi Leute!

Ich bin gerade dabei, mich in die Welt der Zeiger einzuarbeiten.
Wie man z.B. die Adresse eines Feldes, einer Struktur oder einer 
Varibale einem Zeiger zuweisen kann, hab ich, glaub ich, ganz gut 
verstanden ;-)

Dann ist mir eingefallen, was tut man, wenn man ein mehrdimensionales 
Feld hat und darauf einen Zeiger richten möchte? Wie kriegt man das mit 
den verschiedenen Indexen hin?
Ich weiß, dass ein mehrdimensionales Feld an sich eh schon einen Zeiger 
darstellt, aber mir lässt diese Frage zur Zeit keine Ruhe.

Z.B.
uint8_t matrix[3][4][8][10];

uint8_t *matrix_z;

Ich möchte gerne den Zeiger matrix_z auf das mehrdimensionale Feld
matrix zeigen lassen. Dies habe ich folgendermaßen realisiert.

matrix_z=(uint8_t *) matrix;


Wenn ich auf das Feld matrix zugreife, um ein Element zu lesen, dann 
schaffe ich das folgendermaßen: z.B. x=matrix[0][2][4][3];

Wenn ich matrix_z verwenden möchte, dann kann ich dies nur mit einem 
Index tun: z.B. x=matrix_z[10];


Wie kann ich dem Zeiger matrix_z mehrere Indexe beibringen, sodass ich
ihn genauso ansprechen kann wie matrix?
Es würde mich sehr freuen, wenn ihr mir kompetent zur Seite stehen 
könntet.
Vielen Dank im Voraus.

LG

Martin

von Klaus W. (mfgkw)


Lesenswert?

Mit einem Zeiger vom Typ uint8_t* kannst du in der Tat
nur noch mit einem Index auf die Elemente zugreifen.
Die restliche Arithmetik musst du dir dann manuell
bauen (zur Übersicht mit etwas weniger Elementen):
1
// Feld mit 4 Dimensionen 2 * 3 * 4 * 5
2
uint8_t matrix[2][3][4][5] =
3
     {
4
        {
5
           {
6
              {
7
                 1, 1, 1, 1, 1
8
              },
9
              {
10
                 1, 1, 1, 1, 1
11
              },
12
              {
13
                 1, 1, 1, 1, 1
14
              },
15
              {
16
                 1, 1, 1, 1, 1
17
              },
18
           },
19
           {
20
              {
21
                 1, 1, 1, 1, 1
22
              },
23
              {
24
                 1, 1, 1, 1, 1
25
              },
26
              {
27
                 1, 1, 1, 1, 1
28
              },
29
              {
30
                 1, 1, 1, 1, 1
31
              },
32
           },
33
           {
34
              {
35
                 1, 1, 1, 1, 1
36
              },
37
              {
38
                 1, 1, 1, 1, 1
39
              },
40
              {
41
                 1, 1, 1, 1, 1
42
              },
43
              {
44
                 1, 1, 1, 1, 1
45
              },
46
           },
47
        },
48
        {
49
           {
50
              {
51
                 1, 1, 1, 1, 1
52
              },
53
              {
54
                 1, 1, 1, 1, 1
55
              },
56
              {
57
                 1, 1, 1, 1, 1
58
              },
59
              {
60
                 1, 1, 1, 1, 1
61
              },
62
           },
63
           {
64
              {
65
                 1, 1, 1, 1, 1
66
              },
67
              {
68
                 1, 1, 1, 1, 1
69
              },
70
              {
71
                 1, 1, 1, 1, 1
72
              },
73
              {
74
                 1, 1, 1, 1, 1
75
              },
76
           },
77
           {
78
              {
79
                 1, 1, 1, 1, 1
80
              },
81
              {
82
                 1, 1, 1, 1, 1
83
              },
84
              {
85
                 1, 1, 1, 1, 1
86
              },
87
              {
88
                 1, 1, 1, 1, 1
89
              },
90
           },
91
        },
92
     };
93
94
uint8_t *matrix_z = &matrix[0][0][0][0]; // Zeiger auf erstes Element
95
96
97
// Zugriff auf matrix[i][j][k][l]:
98
uint8_t i = 1, j = 2, k = 3, l = 4;
99
uint8_t tmp;
100
101
tmp = matrix[i][j][k][l];
102
tmp = matrix_z[3*4*5*i + 4*5*j + 5*k + l];
103
tmp = matrix_z[((i*3 + j)*4 + k)*5 + l];

Ein ganz anderer Ansatz wäre, Zwischenfelder zu bauen mit
Zeigern auf das jeweils nächste Zwischenfeld:
1
uint8_t matrix[2][3][4][5] =
2
     {
3
        {
4
           {
5
              {
6
                 1, 1, 1, 1, 1
7
              },
8
              {
9
                 1, 1, 1, 1, 1
10
              },
11
              {
12
                 1, 1, 1, 1, 1
13
              },
14
              {
15
                 1, 1, 1, 1, 1
16
              },
17
           },
18
           {
19
              {
20
                 1, 1, 1, 1, 1
21
              },
22
              {
23
                 1, 1, 1, 1, 1
24
              },
25
              {
26
                 1, 1, 1, 1, 1
27
              },
28
              {
29
                 1, 1, 1, 1, 1
30
              },
31
           },
32
           {
33
              {
34
                 1, 1, 1, 1, 1
35
              },
36
              {
37
                 1, 1, 1, 1, 1
38
              },
39
              {
40
                 1, 1, 1, 1, 1
41
              },
42
              {
43
                 1, 1, 1, 1, 1
44
              },
45
           },
46
        },
47
        {
48
           {
49
              {
50
                 1, 1, 1, 1, 1
51
              },
52
              {
53
                 1, 1, 1, 1, 1
54
              },
55
              {
56
                 1, 1, 1, 1, 1
57
              },
58
              {
59
                 1, 1, 1, 1, 1
60
              },
61
           },
62
           {
63
              {
64
                 1, 1, 1, 1, 1
65
              },
66
              {
67
                 1, 1, 1, 1, 1
68
              },
69
              {
70
                 1, 1, 1, 1, 1
71
              },
72
              {
73
                 1, 1, 1, 1, 1
74
              },
75
           },
76
           {
77
              {
78
                 1, 1, 1, 1, 1
79
              },
80
              {
81
                 1, 1, 1, 1, 1
82
              },
83
              {
84
                 1, 1, 1, 1, 1
85
              },
86
              {
87
                 1, 1, 1, 1, 1
88
              },
89
           },
90
        },
91
     };
92
93
uint8_t *matrix_m00[4] = { &matrix[0][0][0][0], &matrix[0][0][1][0], &matrix[0][0][2][0], &matrix[0][0][3][0] };
94
uint8_t *matrix_m01[4] = { &matrix[0][1][0][0], &matrix[0][1][1][0], &matrix[0][1][2][0], &matrix[0][1][3][0] };
95
uint8_t *matrix_m02[4] = { &matrix[0][2][0][0], &matrix[0][2][1][0], &matrix[0][2][2][0], &matrix[0][2][3][0] };
96
uint8_t *matrix_m10[4] = { &matrix[1][0][0][0], &matrix[1][0][1][0], &matrix[1][0][2][0], &matrix[1][0][3][0] };
97
uint8_t *matrix_m11[4] = { &matrix[1][1][0][0], &matrix[1][1][1][0], &matrix[1][1][2][0], &matrix[1][1][3][0] };
98
uint8_t *matrix_m12[4] = { &matrix[1][2][0][0], &matrix[1][2][1][0], &matrix[1][2][2][0], &matrix[1][2][3][0] };
99
100
uint8_t **matrix_l0[3] = { &matrix_m00[0], &matrix_m01[0], &matrix_m02[0] };
101
uint8_t **matrix_l1[3] = { &matrix_m10[0], &matrix_m11[0], &matrix_m12[0] };
102
103
uint8_t ***matrix_k[2] = { &matrix_l0[0], &matrix_l1[0] };
matrix_k hat also zwei Elemente, die jeweils auf den Anfang
einer 3*4*5-Matrix zeigen.

Diese 3*4*5-Matrizen haben je 3 Elemente, die jeweils auf den
Anfang einer 4*5-Matrix zeigen.

Diese 4*5-Matrizen haben je 4 Elemente, die jeweils auf den Anfang
eines 5-Vektors (5 Elemente uint8_t) zeigen.

Diese Vektoren wiederum sind Teile der ursprünglichen matrix[][][][].

Jetzt kann man über matrix_k mit 4 Indices auf ein Element zugreifen:
1
uint8_t i = 1, j = 2, k = 3, l = 4;
2
uint8_t tmp;
3
4
tmp = matrix[i][j][k][l];
5
tmp = matrix_k[i][j][k][l];
Die beiden Anweisungen sehen jetzt verdächtig gleich aus,
sind aber zwei grundverschiedene Dinge:

matrix[i][j][k][l] ist ein Zugriff auf ein Element
eines 4-dimensionalen Feldes.

matrix_k[i][j][k][l] dagegen fasst mit matrix_k[i]
bzw. matrix_k[1] auf das Feld matrix_l1[0], darin mit
dem zweiten Index [j] ([2]) auf matrix_m12, darin
mit dem 3. Index [k] ([3]) auf den entsprechenden
Teilvektor in matrix[][][][].

Auch wenn die beiden eben noch gleich aussahen, merkt
man den Unterschied, wenn man eine Funktion aufruft.
Sowohl mit matrix als auch mit matrix_z (siehe oben)
sähe das so aus:
1
void f1( uint8_t *m )
2
{
3
  uint8_t tmp = m[((i*3 + j)*4 + k)*5 + l];
4
}
5
...
6
  f1( matrix );
7
  f1( matrix_z );
Ggf. müsste man der Funktion auch noch zumindest alle
außer der letzten Dimension mitgeben (hier die 3, 4 und 5
als Konstanten verwendet).
Die erste Dimension (2) muß in der Funktion nicht
zwingend bekannt sein, solange man sicher ist, nicht
über das Feld hinauszugreifen.
Sowohl bei dem Aufruf mit matrix bzw. matrix_z wird
in Wirklichkeit die Adresse von matrix[0][0][0][0]
übergeben.
In beiden Fällen muß manuell die Zeigerarithmetik für
alles über der ersten Dimension gemacht werden.

Dagegen die zweite Variante: mit dem Mehraufwand beim
Anlegen der Felder gewinnt man einen lässigen Zugriff
auf die Elemente auch in einer Funktion.
1
void f2( uint8_t ****m )
2
{
3
  uint8_t  tmp = m[i][j][k][l];
4
}
5
...
6
  f2( matrix_k );

Hier sieht man, daß matrix_k etwas ganz anderes ist
als matrix bzw. matrix_z.
Mit letzteren könnte man f2() nicht aufrufen.

von Klaus W. (mfgkw)


Lesenswert?

Nachtrag 1:
Es gibt noch diese Variante für den Funktionsaufruf:
1
void f3( uint8_t m[][3][4][5] )
2
{
3
  uint8_t  tmp = m[i][j][k][l];
4
}
oder
1
void f3( uint8_t m[2][3][4][5] )
2
{
3
  uint8_t  tmp = m[i][j][k][l];
4
}

Eine solche Funktion kann mit matrix ebenso wie mit
matrix_z aufgerufen werden, aber nicht mit matrix_k
(also nicht mit den Zwischenfeldern).
Beim Aufruf mit matrix_z kommt ggf. eine Warnung des
Compilers, aber es funktioniert trotzdem.

Vorteil dieser Variante:
- der Compiler kann mir wieder die Arithmetik mit den
  mehreren Indices abnehmen

Nachteil:
- alle Dimensionen (ggf. außer der ersten) müssen
  konstant sein, mit anders dimensioonierten Feldern
  geht nichts.

Bei meiner Zwischenfeldvariante (f2()) können sowohl
die 4 Dimensionen unterschiedlich sein von Aufruf zu
Aufruf, ebenso wie die Teilfelder gar nicht einheitlich
lang sein müssen (z.B. für Dreiecksmatrizen) - trotzdem
beherrscht der Compiler die Indexberechnung für alle
Dimensionen.

von Klaus W. (mfgkw)


Lesenswert?

Nachtrag 2 zu der Zwischenfeldversion (f2()):

- wie eben erwähnt muß die Matrix nicht komplett gefüllt sein,
  z.B. kann bei zweidimensionalen Matrizen jede Zeile eine
  andere Länge haben (sparsame Speicherung von Dreiecksmatrizen)

- im ursprünglichen Beispiel liegen die eigentlichen
  Feldelement der Matrix in einem durchgehenden Speicherbereich.
  Das ist eigentlich gar nicht nötig, z.B. könnten für
  eine 2-D-Matrix die Zeilen jeweils getrennt voneinander
  liegen. Dadurch kann der Speicher auch zeilenweise zur
  Laufzeit mit malloc() allokiert werden.

- Die Längen der einzelnen Dimensionen können sich bei
  Bedarf auch erst zur Laufzeit ergeben, sie müssen
  nicht dem Compiler als Konstanten bekannt sein.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Ein Zeiger, der gleich indiziert werden kann wie ein Array, ist ein
Zeiger auf das erste Array-Element. Bei einem eindimensionalen
uint8_t-Array ist der Zeiger also einfach ein Zeiger auf uint8_t:
1
uint8_t vektor[10];
2
uint8_t *vektor_z = vektor;
3
4
// Zugriff:
5
  uint8_t x;
6
  x = vektor[3];
7
  x = vektor_z[3];  // Kurzschreibweise für x = *(vektor_z + 3)

Mehrdimensionale Arrays gibt es in C eigentlich gar nicht. Statt einem
n-dimensionalen Array verwendet man in Wirklichkeit ein Array von
(n-1)-dimensionalen Unterarrays. Dewegen kann das oben für vektor
Geschriebene auch auf mehrdimensionale Arrays übertragen werden.

Die Definition
1
uint8_t matrix[8][10];

legt also ein Array mit 8 Elementen an, von denen jedes ein Array von 10
uint8_t-Werten ist.

Der Zugriff
1
x = matrix[4][3];

ermittelt also zuerst das Element von matrix mit dem Index 4. Dieses
Element ist ein uint8_t-Array, von dem schließlich das Element mit dem
Index 3 gelesen wird.

Wenn man das verstanden hat, ist es nicht mehr schwer, einen zum Array
matrix äquivalenten Zeiger zu definieren. Der Elementdatentyp von
matrix ist (uint8_t [10]), nämlich ein Array aus 10 uint8_t-Werten.
Also wird der Zeiger wie folgt definiert:
1
uint8_t (*matrix_z)[10] = matrix;

Die Klammern um *matrix sind wichtig, weil der []-Operator stärker als
der *-Operator bindet.

Damit ist matrix_z ein Zeiger auf das erste Element (Unterarray) von
matrix und *matrix_z das Unterarray selber. Statt *matrix_z kann man
schöner auch matrix_z[0] schreiben. Mit
1
  x = matrix_z[4][3]; // entspricht x = (*(matrix + 4))[3]

wird also aus dem Unterarray matrix[4] das uint8_t-Element mit dem Index
3 gelesen. Das ist genau das, was gewünscht ist.

Beim vierdimensionalen Array wird in entsprechender Weise ein Zeiger auf
ein dreidimensionales Array angelegt:
1
uint8_t array4[3][4][8][10];
2
uint8_t (*array4_z)[4][8][10] = array4;
3
4
// Zugriff:
5
  uint8_t x;
6
  x = array4[0][2][4][3];
7
  x = array4_z[0][2][4][3];

Das ist alles.


Klaus hat oben gezeigt, wie man ein mehrdimensionales Array als
Funktionsargument übergibt:

Klaus Wachtler schrieb:
> Es gibt noch diese Variante für den Funktionsaufruf:
1
void f3( uint8_t m[][3][4][5] )
2
{
3
  uint8_t  tmp = m[i][j][k][l];
4
}

Auch hier wird letztendlich ein Zeiger auf ein dreidimensionales Array
übergeben. Nur wird dort in der ausführlichen Schreibweise
1
void f3( uint8_t (*m)[3][4][5] )

das (*m) durch das äquivalente m[] ersetzt, was speziell bei der Dekla-
ration von Formalparametern zulässig ist und zum Ausdruck bringt, dass
m nicht irgendein Zeiger ist, sondern auf ein real im Speicher exis-
tierendes Array zeigen soll.

von Martin (Gast)


Lesenswert?

Hi Leute!

Danke für eure tollen Erklärungen.
Klaus: Dein geniales Beispiel hat mir sehr anschaulich gezeigt, was man 
mit Zeigerfeldern alles machen kann. Dadurch habe ich wieder viel 
gelernt.
Yalu: Deine Erklärungen war sehr aufschlussreicht und haben meine Fragen 
restlos beantwortet. Dadurch sind mir einige Lichter aufgegangen.

Ich habe versucht, verschiedene Zugriffsmöglichkeiten zusammenzufassen, 
um sie hier bereitzustellen.
1
#include <utility.h>
2
#include<stdio.h>
3
4
typedef unsigned char uint8_t;
5
typedef signed char int8_t;
6
typedef unsigned short uint16_t;
7
typedef signed short int16_t;
8
typedef unsigned long uint32_t;
9
typedef signed long int32_t;
10
11
12
uint8_t matrix_feld[2][3][4][5]; // Originalfeld
13
14
uint8_t *matrix_z;               // Ein Zeiger, der auf ein Feld zeigt (Hat jedoch nur einen Index.).
15
uint8_t *matrix_zfeld1[2][3][4][5]; // Ein mehrdimensionales Zeigerfeld. Jedes Element ist ein Zeiger. Jeder Zeiger zeigt auf ein einzelnes Element eines Feldes.
16
uint8_t *matrix_zfeld2[2][3][4]; // Ein mehrdimensionales Zeigerfeld. Jedes Element ist ein Zeiger. Jeder Zeiger zeigt auf das erste Element eines Feldes. 
17
uint8_t (*matrix_zindex)[3][4][5]; // Ein Zeiger, der auf ein Feld zeigt. Der Zeiger erhält entsprechende Index-Infos, damit er weiß, wie das Feld aussieht.
18
19
uint8_t zc1,zc2,zc3,zc4;
20
const uint8_t zx1=2,zx2=3,zx3=4,zx4=5;
21
uint8_t wert;
22
23
24
25
26
void main(void)
27
{
28
29
// Zuweisung, Befüllung
30
for(zc1=0,wert=0;zc1<zx1;zc1++)
31
{
32
  for(zc2=0;zc2<zx2;zc2++)
33
  {
34
    for(zc3=0;zc3<zx3;zc3++)
35
    {
36
      matrix_zfeld2[zc1][zc2][zc3]=matrix_feld[zc1][zc2][zc3];   // zfeld2 wird mit Adressen befüllt.
37
      for(zc4=0;zc4<zx4;zc4++,wert++)
38
      {
39
        matrix_feld[zc1][zc2][zc3][zc4]=wert;           // Originalfeld (feld) wird mit Werten befüllt.
40
        matrix_zfeld1[zc1][zc2][zc3][zc4]=&matrix_feld[zc1][zc2][zc3][zc4]; // zfeld1 wird mit Adressen befüllt.
41
      }
42
    }
43
  }
44
}
45
46
matrix_z=(uint8_t *) matrix_feld;   // z erhält die Adresse des Originalfeldes.
47
matrix_zindex=matrix_feld;          // zindex erhält die Adresse des Originalfeldes.
48
49
// Ausgabe
50
for(zc1=0;zc1<zx1;zc1++)
51
{
52
  for(zc2=0;zc2<zx2;zc2++)
53
  {
54
    for(zc3=0;zc3<zx3;zc3++)
55
    {
56
      for(zc4=0;zc4<zx4;zc4++)
57
      {
58
        printf("%d:%d:%d:%d   %d - %d - %d - %d - %d - %d\r\n"
59
      ,zc1,zc2,zc3,zc4                                      // Index
60
      ,matrix_feld[zc1][zc2][zc3][zc4]                          // Originalfeld
61
    ,matrix_z[(zx2*zx3*zx4*zc1)+(zx3*zx4*zc2)+(zx4*zc3)+zc4]  // Zeiger, der auf ein Array zeigt.
62
    ,*matrix_zfeld1[zc1][zc2][zc3][zc4]                       // Feld von Zeigern. Ein Zeiger zeigt auf ein einzelnes Element.
63
        ,matrix_zfeld2[zc1][zc2][zc3][zc4]                        // Feld von Zeigern. Ein Zeiger zeigt jeweils auf das erste Element.
64
        ,matrix_zindex[zc1][zc2][zc3][zc4]                        // Zeiger, der auf ein definiertes Array zeigt.
65
    ,(*(matrix_zindex+zc1))[zc2][zc3][zc4]);                  // Zeiger, der auf ein definiertes Array zeigt. Zugriff auf eine andere Art.
66
      }
67
    }
68
  }
69
}
70
71
GetKey();
72
}

Falls etwas an meiner Zusammenfassung nicht stimmen sollte, bitte ich, 
Bescheid zu geben.


LG

Martin

von Klaus W. (mfgkw)


Lesenswert?

Yalu X. schrieb:
> Klaus Wachtler schrieb:
>> Es gibt noch diese Variante für den Funktionsaufruf:void f3( uint8_t 
m[][3][4][5] )
> {
>   uint8_t  tmp = m[i][j][k][l];
> }
>
> Auch hier wird letztendlich ein Zeiger auf ein dreidimensionales Array
> übergeben. Nur wird dort in der ausführlichen Schreibweise
> void f3( uint8_t (*m)[3][4][5] )
>
> das (*m) durch das äquivalente m[] ersetzt, was speziell bei der Dekla-
> ration von Formalparametern zulässig ist und zum Ausdruck bringt, dass
> m nicht irgendein Zeiger ist, sondern auf ein real im Speicher exis-
> tierendes Array zeigen soll.

Ja.

Wobei sich uint8_t m[][3][4][5] und uint8_t (*m)[3][4][5]
in einem Punkt unterscheiden:
Im ersten Fall ist m konstant, in der zweiten eine Variable
(wenn man nicht noch ein const spendiert).
Beides kann sinnvoll sein.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.