Erstellen Sie einen Zeiger auf ein zweidimensionales Array

119

Ich brauche einen Zeiger auf ein statisches zweidimensionales Array. Wie wird das gemacht?

static uint8_t l_matrix[10][20];

void test(){
   uint8_t **matrix_ptr = l_matrix; //wrong idea 
}

Ich bekomme alle Arten von Fehlern wie:

  • Warnung: Zuweisung vom inkompatiblen Zeigertyp
  • indizierter Wert ist weder Feld noch ein Zeiger
  • Fehler: Ungültige Verwendung des flexiblen Array-Mitglieds
Dill
quelle
Lesen Sie stackoverflow.com/questions/423823/… es kann Ihnen helfen
Johannes Schaub - litb
1
@ JohannesSchaub-litb Das gibt es nicht mehr. (Wie sehe ich es wieder an ...? Ich weiß, dass Mitglieder mit niedrigen
Wiederholungszahlen
@muntoo: Hier ist eine Kopie davon: gist.github.com/sharth/ede13c0502d5dd8d45bd
Bill Lynch

Antworten:

139

Hier möchten Sie einen Zeiger auf das erste Element des Arrays erstellen

uint8_t (*matrix_ptr)[20] = l_matrix;

Mit typedef sieht das sauberer aus

typedef uint8_t array_of_20_uint8_t[20];
array_of_20_uint8_t *matrix_ptr = l_matrix;

Dann kannst du das Leben wieder genießen :)

matrix_ptr[0][1] = ...;

Hüten Sie sich vor der Zeiger- / Array-Welt in C, da herrscht große Verwirrung.


Bearbeiten

Überprüfen Sie einige der anderen Antworten hier, da die Kommentarfelder zu kurz sind, um sie dort auszuführen. Es wurden mehrere Alternativen vorgeschlagen, aber es wurde nicht gezeigt, wie sie sich verhalten. So machen sie es

uint8_t (*matrix_ptr)[][20] = l_matrix;

Wenn Sie den Fehler beheben und die Adresse des Operators &wie im folgenden Snippet hinzufügen

uint8_t (*matrix_ptr)[][20] = &l_matrix;

Dann erstellt dieser einen Zeiger auf einen unvollständigen Array-Typ von Elementen vom Typ Array von 20 uint8_t. Da sich der Zeiger auf ein Array von Arrays befindet, müssen Sie mit darauf zugreifen

(*matrix_ptr)[0][1] = ...;

Und weil es ein Zeiger auf ein unvollständiges Array ist, können Sie es nicht als Verknüpfung verwenden

matrix_ptr[0][0][1] = ...;

Da für die Indizierung die Größe des Elementtyps bekannt sein muss (die Indizierung impliziert das Hinzufügen einer Ganzzahl zum Zeiger, sodass dies bei unvollständigen Typen nicht funktioniert). Beachten Sie, dass dies nur funktioniert , in C, weil T[]und T[N]kompatible Typen sind. C ++ hat kein Konzept von kompatibelen Typen , und so ist es , dass Code ablehnen, weil T[]und T[10]verschiedene Arten sind.


Die folgende Alternative funktioniert überhaupt nicht, da der Elementtyp des Arrays, wenn Sie es als eindimensionales Array betrachten, dies nicht uint8_t istuint8_t[20]

uint8_t *matrix_ptr = l_matrix; // fail

Das Folgende ist eine gute Alternative

uint8_t (*matrix_ptr)[10][20] = &l_matrix;

Sie greifen mit darauf zu

(*matrix_ptr)[0][1] = ...;
matrix_ptr[0][0][1] = ...; // also possible now

Es hat den Vorteil, dass die Größe der äußeren Abmessung erhalten bleibt. Sie können also sizeof darauf anwenden

sizeof (*matrix_ptr) == sizeof(uint8_t) * 10 * 20

Es gibt eine andere Antwort, die die Tatsache nutzt, dass Elemente in einem Array zusammenhängend gespeichert werden

uint8_t *matrix_ptr = l_matrix[0];

Damit können Sie formal nur auf die Elemente des ersten Elements des zweidimensionalen Arrays zugreifen. Das heißt, die folgende Bedingung gilt

matrix_ptr[0] = ...; // valid
matrix_ptr[19] = ...; // valid

matrix_ptr[20] = ...; // undefined behavior
matrix_ptr[10*20-1] = ...; // undefined behavior

Sie werden feststellen, dass es wahrscheinlich funktioniert 10*20-1, aber wenn Sie Alias-Analysen und andere aggressive Optimierungen verwenden, könnte ein Compiler eine Annahme treffen, die diesen Code möglicherweise beschädigt. Trotzdem bin ich noch nie auf einen Compiler gestoßen, der darauf fehlschlägt (aber andererseits habe ich diese Technik nicht in echtem Code verwendet), und selbst in den C-FAQ ist diese Technik enthalten (mit einer Warnung über seine UB'ness) ), und wenn Sie den Array-Typ nicht ändern können, ist dies eine letzte Option, um Sie zu speichern :)

Johannes Schaub - litb
quelle
+1 - nette Infos zur int (*) [] [20] Aufschlüsselung - kann das in C ++ nicht
Faisal Vali
@litb, es tut mir leid, aber das ist falsch, da Ihre Lösung keine Speicherzuweisung für das Array bietet.
Rob Wells
2
@Rob, ich verstehe dich nicht ganz. Der Speicher wird in all diesen Fällen vom Array l_matix selbst bereitgestellt. Die Zeiger auf sie werden von jedem Ort aus gespeichert, an dem sie deklariert sind (Stapel, statisches Datensegment, ...).
Johannes Schaub - litb
Nur neugierig, warum brauchen wir "&" Adresse von l_matrix?
Elektro
1
@ Sohaib - nein, das schafft nur einen Zeiger. Sie haben es vielleicht mit verwechselt uint8_t *d[20], wodurch ein Array von 3 Zeigern auf uint8_t erstellt wird, aber das würde in diesem Fall nicht funktionieren.
Palo
27

Um dies vollständig zu verstehen, müssen Sie die folgenden Konzepte verstehen :

Arrays sind keine Zeiger!

Erstens (und es wurde genug gepredigt) sind Arrays keine Zeiger . Stattdessen "zerfallen" sie in den meisten Fällen in die Adresse ihres ersten Elements, das einem Zeiger zugewiesen werden kann:

int a[] = {1, 2, 3};

int *p = a; // p now points to a[0]

Ich gehe davon aus, dass es so funktioniert, dass auf den Inhalt des Arrays zugegriffen werden kann, ohne alle zu kopieren. Das ist nur ein Verhalten von Array-Typen und soll nicht bedeuten, dass sie dasselbe sind.



Mehrdimensionale Arrays

Mehrdimensionale Arrays sind nur eine Möglichkeit, den Speicher so zu partitionieren, dass der Compiler / die Maschine ihn verstehen und bearbeiten kann.

Zum Beispiel int a[4][3][5]= ein Array, das 4 * 3 * 5 (60) 'Chunks' von ganzzahligem Speicher enthält.

Der Vorteil gegenüber der Verwendung von int a[4][3][5]vs plain int b[60]besteht darin, dass sie jetzt "partitioniert" sind (bei Bedarf einfacher mit ihren "Chunks" zu arbeiten) und das Programm nun gebundene Überprüfungen durchführen kann.

Tatsächlich int a[4][3][5]wird genau wie int b[60]im Speicher gespeichert - Der einzige Unterschied besteht darin, dass das Programm es jetzt so verwaltet, als ob es sich um separate Entitäten bestimmter Größen handelt (insbesondere vier Gruppen zu drei Gruppen zu je fünf).

Denken Sie daran: Beide int a[4][3][5]und int b[60]sind im Speicher gleich, und der einzige Unterschied besteht darin, wie sie von der Anwendung / dem Compiler behandelt werden

{
  {1, 2, 3, 4, 5}
  {6, 7, 8, 9, 10}
  {11, 12, 13, 14, 15}
}
{
  {16, 17, 18, 19, 20}
  {21, 22, 23, 24, 25}
  {26, 27, 28, 29, 30}
}
{
  {31, 32, 33, 34, 35}
  {36, 37, 38, 39, 40}
  {41, 42, 43, 44, 45}
}
{
  {46, 47, 48, 49, 50}
  {51, 52, 53, 54, 55}
  {56, 57, 58, 59, 60}
}

Daraus können Sie deutlich erkennen, dass jede "Partition" nur ein Array ist, das das Programm verfolgt.



Syntax

Jetzt unterscheiden sich Arrays syntaktisch von Zeigern . Dies bedeutet insbesondere, dass der Compiler / die Maschine sie unterschiedlich behandelt. Dies mag wie ein Kinderspiel erscheinen, aber sehen Sie sich Folgendes an:

int a[3][3];

printf("%p %p", a, a[0]);

Im obigen Beispiel wird dieselbe Speicheradresse zweimal wie folgt gedruckt:

0x7eb5a3b4 0x7eb5a3b4

Einem Zeiger kann jedoch nur einer so direkt zugewiesen werden :

int *p1 = a[0]; // RIGHT !

int *p2 = a; // WRONG !

Warum kann kein a Zeiger zugewiesen werden, kann es aber a[0] ?

Dies ist einfach eine Folge mehrdimensionaler Arrays, und ich werde erklären, warum:

Auf der Ebene von ' a' sehen wir immer noch, dass wir eine andere 'Dimension' haben, auf die wir uns freuen können. Auf der Ebene von ' a[0]' befinden wir uns jedoch bereits in der obersten Dimension, was das Programm betrifft, betrachten wir nur ein normales Array.

Sie fragen sich vielleicht:

Warum ist es wichtig, wenn das Array mehrdimensional ist, um einen Zeiger dafür zu erstellen?

Es ist am besten, so zu denken:

Ein "Zerfall" von einem mehrdimensionalen Array ist nicht nur eine Adresse, sondern eine Adresse mit Partitionsdaten (AKA versteht immer noch, dass die zugrunde liegenden Daten aus anderen Arrays bestehen), die aus Grenzen besteht, die vom Array über die erste Dimension hinaus festgelegt werden.

Diese 'Partitions'-Logik kann nicht innerhalb eines Zeigers existieren, es sei denn, wir geben sie an:

int a[4][5][95][8];

int (*p)[5][95][8];

p = a; // p = *a[0] // p = a+0

Andernfalls geht die Bedeutung der Sortiereigenschaften des Arrays verloren.

Beachten Sie auch die Verwendung von Klammern um *p: int (*p)[5][95][8]- Dies bedeutet, dass wir einen Zeiger mit diesen Grenzen erstellen, nicht ein Array von Zeigern mit diesen Grenzen:int *p[5][95][8]



Fazit

Lassen Sie uns überprüfen:

  • Arrays zerfallen in Adressen, wenn sie im verwendeten Kontext keinen anderen Zweck haben
  • Mehrdimensionale Arrays sind nur Arrays von Arrays. Daher trägt die "verfallene" Adresse die Last "Ich habe Unterdimensionen".
  • Dimensionsdaten können in einem Zeiger nur vorhanden sein, wenn Sie sie ihm geben .

Kurz gesagt: Mehrdimensionale Arrays zerfallen in Adressen, die den Inhalt verstehen können.

Super Cat
quelle
1
Der erste Teil der Antwort ist großartig, der zweite nicht. Dies ist nicht korrekt: int *p1 = &(a[0]); // RIGHT !Eigentlich ist es identisch mitint *p1 = a;
2501
@ 2501 Vielen Dank, dass Sie diesen Fehler entdeckt haben. Ich habe ihn korrigiert. Ich kann nicht mit Sicherheit sagen, warum das Beispiel, das diese "Regel" definiert, sich ihr auch widersetzte. Nur weil zwei Entitäten als Zeiger interpretiert werden können und denselben Wert liefern, bedeutet dies nicht, dass sie dieselbe Bedeutung haben.
Super Cat
7

Im

int *ptr= l_matrix[0];

Sie können gerne darauf zugreifen

*p
*(p+1)
*(p+2)

Immerhin werden zweidimensionale Arrays auch als 1-d gespeichert.

Sagar
quelle
5

Tag auch,

Die Erklärung

static uint8_t l_matrix[10][20];

hat Speicher für 10 Zeilen mit 20 unit8_t-Positionen reserviert, dh 200 uint8_t-Positionen, wobei jedes Element durch Berechnung von 20 x Zeile + Spalte gefunden wird.

Also nicht

uint8_t (*matrix_ptr)[20] = l_matrix;

Geben Sie an, was Sie benötigen, und zeigen Sie auf das Element der Spalte Null in der ersten Zeile des Arrays.

Bearbeiten: Wenn Sie etwas weiter darüber nachdenken, ist ein Array-Name per Definition kein Zeiger? Das heißt, der Name eines Arrays ist ein Synonym für die Position des ersten Elements, dh l_matrix [0] [0]?

Edit2: Wie von anderen erwähnt, ist der Kommentarbereich für weitere Diskussionen etwas zu klein. Wie auch immer:

typedef uint8_t array_of_20_uint8_t[20];
array_of_20_uint8_t *matrix_ptr = l_matrix;

bietet keine Speicherzuweisung für das betreffende Array.

Wie oben erwähnt und wie in der Norm definiert, lautet die Aussage:

static uint8_t l_matrix[10][20];

hat 200 aufeinanderfolgende Speicherorte vom Typ uint8_t reserviert.

Verweis auf l_matrix unter Verwendung von Anweisungen der Form:

(*l_matrix + (20 * rowno) + colno)

gibt Ihnen den Inhalt des colno'ten Elements in Zeile rowno.

Bei allen Zeigermanipulationen wird automatisch die Größe des Objekts berücksichtigt, auf das verwiesen wird. - K & R Abschnitt 5.4, S.103

Dies ist auch der Fall, wenn eine Auffüllung oder Verschiebung der Byte-Ausrichtung an der Speicherung des vorliegenden Objekts beteiligt ist. Der Compiler passt diese automatisch an. Per Definition des C ANSI-Standards.

HTH

Prost,

Rob Wells
quelle
1
uint8_t (* matrix_ptr) [] [20] << die ersten Klammern müssen weggelassen werden, richtig ist uint8_t (* matrix_ptr) [20]
Aconcagua
5

In C99 (unterstützt von clang und gcc) gibt es eine obskure Syntax zum Übergeben mehrdimensionaler Arrays an Funktionen als Referenz:

int l_matrix[10][20];

void test(int matrix_ptr[static 10][20]) {
}

int main(void) {
    test(l_matrix);
}

Im Gegensatz zu einem einfachen Zeiger weist dies auf die Arraygröße hin, sodass der Compiler theoretisch vor dem Übergeben eines zu kleinen Arrays warnen und einen offensichtlichen Zugriff außerhalb der Grenzen erkennen kann.

Leider wird es nicht behoben sizeof()und Compiler scheinen diese Informationen noch nicht zu verwenden, so dass es eine Kuriosität bleibt.

Kornel
quelle
1
Diese Antwort ist irreführend: Das macht das Argument nicht zu einem Array mit fester Größe, es ist immer noch ein Zeiger. static 10ist eine Art Garantie dafür, dass mindestens 10 Elemente vorhanden sind, was wiederum bedeutet, dass die Größe nicht festgelegt ist.
Bluss
1
@bluss Die Frage betraf einen Zeiger, daher sehe ich nicht, wie irreführend die Beantwortung mit einem Zeiger ( unter Bezugnahme ) ist. Das Array hat aus Sicht der Funktion eine feste Größe, da der Zugriff auf Elemente außerhalb dieser Grenzen nicht definiert ist.
Kornel
Ich denke nicht, dass der Zugriff über 10 hinaus undefiniert ist. Ich kann nichts sehen, was darauf hinweist.
Bluss
Diese Antwort scheint darauf hinzudeuten static, dass das Array ohne das Schlüsselwort nicht als Referenz übergeben würde, was nicht wahr ist. Arrays werden ohnehin als Referenz übergeben. Die ursprüngliche Frage bezog sich auf einen anderen Anwendungsfall - den Zugriff auf Elemente eines 2D-Arrays mithilfe eines zusätzlichen Zeigers innerhalb derselben Funktion / demselben Namespace.
Palo
4

Sie können jederzeit vermeiden, mit dem Compiler herumzuspielen, indem Sie das Array als linear deklarieren und die Berechnung des Array-Index (Zeile, Spalte) selbst durchführen.

static uint8_t l_matrix[200];

void test(int row, int col, uint8_t val)

{

   uint8_t* matrix_ptr = l_matrix;
   matrix_ptr [col+y*row] = val; // to assign a value

}

Das hätte der Compiler sowieso getan.

Gnosis
quelle
1
Dies macht der C-Compiler sowieso. C hat keine wirkliche Vorstellung von einem "Array" - die [] Notation ist nur syntaktischer Zucker für die Zeigerarithmetik
Ken Keenan
7
Diese Lösung hat den Nachteil, nie den richtigen Weg zu finden.
Craig McQueen
2

Die grundlegende Syntax zum Initialisieren des Zeigers, der auf ein multidimentionales Array verweist, lautet

type (*pointer)[1st dimension size][2nd dimension size][..] = &array_name

Die grundlegende Syntax für den Aufruf lautet

(*pointer_name)[1st index][2nd index][...]

Hier ist ein Beispiel:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
   // The multidimentional array...
   char balance[5][100] = {
       "Subham",
       "Messi"
   };

   char (*p)[5][100] = &balance; // Pointer initialization...

   printf("%s\n",(*p)[0]); // Calling...
   printf("%s\n",(*p)[1]); // Calling...

  return 0;
}

Ausgabe ist:

Subham
Messi

Es funktionierte...

Subham Debnath
quelle
1

Sie können es so machen:

uint8_t (*matrix_ptr)[10][20] = &l_matrix;
Nick Dandoulakis
quelle
1
belegt dies nicht 10 * 20 Bytes RAM? (
Dill
Es belegt 4 Byte oder eine beliebige Größe, die ein Zeiger in Ihrer Box hat. Aber denken Sie daran, wenn Sie diese haben, müssen Sie mit matrix_ptr [0] [x] [y] oder (* matrix_ptr) [x] [y] indizieren. Es ist die direkte und
wortweise
Danke litb, ich habe vergessen zu erwähnen, wie man darauf zugreift. Es macht keinen Sinn, meine Antwort zu bearbeiten, da Sie mit Ihrer Antwort großartige Arbeit geleistet haben :)
Nick Dandoulakis
Besetzt dies also 10 * 20 Bytes RAM oder nicht?
Danijel
@Danijel, da es sich um einen Zeiger auf ein zweidimensionales Array handelt, belegt es nur 4 Bytes oder die Größe eines Zeigers in Ihrer Box, dh 16 Bit, 32 Bit, 64 Bit usw.
Nick Dandoulakis
1

Sie möchten einen Zeiger auf das erste Element, also;

static uint8_t l_matrix[10][20];

void test(){
   uint8_t *matrix_ptr = l_matrix[0]; //wrong idea 
}
Ken Keenan
quelle
0

Sie können auch einen Offset hinzufügen, wenn Sie negative Indizes verwenden möchten:

uint8_t l_matrix[10][20];
uint8_t (*matrix_ptr)[20] = l_matrix+5;
matrix_ptr[-4][1]=7;

Wenn Ihr Compiler einen Fehler oder eine Warnung ausgibt, können Sie Folgendes verwenden:

uint8_t (*matrix_ptr)[20] = (uint8_t (*)[20]) l_matrix;
Mathengineer
quelle
Hallo. Diese Frage ist mit markiert c, daher sollte die Antwort in derselben Sprache sein. Bitte beachten Sie die Tags.
2501