Crea un puntatore a una matrice bidimensionale


120

Ho bisogno di un puntatore a un array bidimensionale statico. Come si fa?

static uint8_t l_matrix[10][20];

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

Ottengo tutti i tipi di errori come:

  • avviso: assegnazione da un tipo di puntatore incompatibile
  • il valore con indice non è né array né puntatore
  • errore: utilizzo non valido del membro dell'array flessibile


1
@ JohannesSchaub-litb Quello non esiste più. (Come posso vederlo di nuovo ...? So che i membri con basso numero di ripetizioni possono visualizzarlo, ma ho dimenticato come ...)
Mateen Ulhaq

Risposte:


140

Qui vuoi creare un puntatore al primo elemento dell'array

uint8_t (*matrix_ptr)[20] = l_matrix;

Con typedef, questo sembra più pulito

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

Quindi puoi goderti di nuovo la vita :)

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

Attenzione al mondo del puntatore / array in C, c'è molta confusione intorno a questo.


modificare

Riesaminare alcune delle altre risposte qui, perché i campi dei commenti sono troppo brevi per essere inseriti lì. Sono state proposte più alternative, ma non è stato mostrato come si comportano. Ecco come lo fanno

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

Se correggi l'errore e aggiungi l'operatore address-of &come nel seguente frammento

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

Quindi quello crea un puntatore a un tipo di array incompleto di elementi di tipo array di 20 uint8_t. Poiché il puntatore è a un array di array, devi accedervi con

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

E poiché è un puntatore a un array incompleto, non puoi fare come scorciatoia

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

Perché l'indicizzazione richiede che la dimensione del tipo di elemento sia nota (l'indicizzazione implica l'aggiunta di un numero intero al puntatore, quindi non funzionerà con i tipi incompleti). Nota che questo funziona solo in C, perché T[]e T[N]sono tipi compatibili. Il C ++ non ha un concetto di tipi compatibili e quindi rifiuterà quel codice, perché T[]e T[10]sono tipi diversi.


La seguente alternativa non funziona affatto, perché il tipo di elemento dell'array, quando lo visualizzi come array unidimensionale, non lo è uint8_t, mauint8_t[20]

uint8_t *matrix_ptr = l_matrix; // fail

Quella che segue è una buona alternativa

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

Puoi accedervi con

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

Ha il vantaggio di preservare le dimensioni della dimensione esterna. Quindi puoi applicare sizeof su di esso

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

C'è un'altra risposta che fa uso del fatto che gli elementi in un array sono archiviati in modo contiguo

uint8_t *matrix_ptr = l_matrix[0];

Ora, questo formalmente ti consente solo di accedere agli elementi del primo elemento della matrice bidimensionale. Cioè, vale la seguente condizione

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

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

Noterai che probabilmente funziona fino a 10*20-1, ma se esegui l'analisi degli alias e altre ottimizzazioni aggressive, alcuni compilatori potrebbero supporre che potrebbero rompere quel codice. Detto questo, non ho mai incontrato un compilatore che fallisce su di esso (ma poi di nuovo, non ho usato quella tecnica nel codice reale), e anche la C FAQ contiene quella tecnica (con un avvertimento sulla sua UB'ness ), e se non puoi cambiare il tipo di array, questa è l'ultima opzione per salvarti :)


+1 - belle informazioni sulla ripartizione int (*) [] [20] - non posso farlo in C ++
Faisal Vali

@litb, mi dispiace ma è sbagliato in quanto la tua soluzione non fornisce alcuna allocazione di memoria per l'array.
Rob Wells,

2
@ Rob, non ti capisco bene. l'archiviazione in tutti questi casi è fornita dall'array l_matix stesso. I puntatori a loro prendono spazio da dove sono dichiarati e come (stack, segmento di dati statici, ...).
Johannes Schaub - litb

Solo curioso perché abbiamo bisogno dell'indirizzo "&" di l_matrix?
electro

1
@ Sohaib - no, questo crea solo un puntatore. Potresti averlo confuso con uint8_t *d[20], che crea un array di 3 puntatori a uint8_t, ma in questo caso non funzionerebbe.
Palo

28

Per comprenderlo appieno , devi comprendere i seguenti concetti:

Gli array non sono puntatori!

Prima di tutto (ed è stato predicato abbastanza), gli array non sono puntatori . Invece, nella maggior parte degli usi, "decadono" all'indirizzo al loro primo elemento, che può essere assegnato a un puntatore:

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

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

Presumo che funzioni in questo modo in modo che sia possibile accedere ai contenuti dell'array senza copiarli tutti. Questo è solo un comportamento dei tipi di array e non intende implicare che siano la stessa cosa.



Array multidimensionali

Gli array multidimensionali sono solo un modo per "partizionare" la memoria in un modo che il compilatore / macchina possa capire e su cui operare.

Ad esempio, int a[4][3][5]= un array contenente 4 * 3 * 5 (60) "blocchi" di memoria di dimensioni intere.

Il vantaggio rispetto all'uso di int a[4][3][5]vs plain int b[60]è che ora sono "partizionati" (è più facile lavorare con i loro "blocchi", se necessario) e il programma può ora eseguire il controllo del binding.

In effetti, int a[4][3][5]viene memorizzato esattamente come int b[60]in memoria - L' unica differenza è che il programma ora lo gestisce come se fossero entità separate di determinate dimensioni (in particolare, quattro gruppi di tre gruppi di cinque).

Tieni presente: entrambi int a[4][3][5]e int b[60]sono uguali in memoria e l'unica differenza è come vengono gestiti dall'applicazione / compilatore

{
  {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}
}

Da questo, puoi vedere chiaramente che ogni "partizione" è solo un array di cui il programma tiene traccia.



Sintassi

Ora, gli array sono sintatticamente diversi dai puntatori . In particolare, questo significa che il compilatore / macchina li tratterà in modo diverso. Questo può sembrare un gioco da ragazzi, ma dai un'occhiata a questo:

int a[3][3];

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

L'esempio precedente stampa lo stesso indirizzo di memoria due volte, in questo modo:

0x7eb5a3b4 0x7eb5a3b4

Tuttavia, solo uno può essere assegnato a un puntatore in modo così diretto :

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

int *p2 = a; // WRONG !

Perché non può a essere assegnato a un puntatore ma a[0] può?

Questo, semplicemente, è una conseguenza degli array multidimensionali e spiegherò perché:

A livello di " a", vediamo ancora di avere un'altra "dimensione" a cui guardare. A livello di " a[0]", tuttavia, siamo già nella dimensione superiore, quindi per quanto riguarda il programma stiamo solo guardando un array normale.

Potresti chiedere:

Perché è importante se l'array è multidimensionale per quanto riguarda la creazione di un puntatore per esso?

È meglio pensare in questo modo:

Un "decadimento" da un array multidimensionale non è solo un indirizzo, ma un indirizzo con dati di partizione (AKA capisce ancora che i suoi dati sottostanti sono costituiti da altri array), che consiste di limiti impostati dall'array oltre la prima dimensione.

Questa logica di "partizione" non può esistere all'interno di un puntatore a meno che non venga specificata:

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

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

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

In caso contrario, il significato delle proprietà di ordinamento dell'array viene perso.

Nota anche l'uso delle parentesi intorno a *p: int (*p)[5][95][8]- Questo per specificare che stiamo creando un puntatore con questi limiti, non un array di puntatori con questi limiti:int *p[5][95][8]



Conclusione

Ripassiamo:

  • Gli array decadono in indirizzi se non hanno altro scopo nel contesto utilizzato
  • Gli array multidimensionali sono solo array di array - Quindi, l'indirizzo 'decaduto' porterà il fardello di "Ho sottodimensioni"
  • I dati di dimensione non possono esistere in un puntatore a meno che non gli vengano forniti .

In breve: gli array multidimensionali decadono in indirizzi che hanno la capacità di comprenderne il contenuto.


1
La prima parte della risposta è ottima, ma la seconda no. Questo non è corretto: in int *p1 = &(a[0]); // RIGHT !realtà è identico aint *p1 = a;
2501

@ 2501 Grazie per aver individuato l'errore, l'ho corretto. Non posso dire con certezza perché l'esempio che definisce questa "regola" l'abbia anche sfidato. Vale la pena ribadire che solo perché due entità possono essere interpretate come indicatori e producono lo stesso valore, non significa che abbiano lo stesso significato.
Super Cat

7

Nel

int *ptr= l_matrix[0];

puoi accedere come

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

dopo che tutti gli array bidimensionali sono anche memorizzati come 1-d.


5

Buongiorno,

La dichiarazione

static uint8_t l_matrix[10][20];

ha riservato lo spazio di archiviazione per 10 righe di 20 posizioni unit8_t, ovvero 200 posizioni di dimensioni uint8_t, con ogni elemento trovato calcolando 20 x riga + colonna.

Quindi no

uint8_t (*matrix_ptr)[20] = l_matrix;

ti dà quello che ti serve e punta all'elemento zero della colonna della prima riga dell'array?

Modifica: pensandoci un po 'oltre, il nome di un array non è, per definizione, un puntatore? Cioè, il nome di un array è un sinonimo per la posizione del primo elemento, cioè l_matrix [0] [0]?

Edit2: come accennato da altri, lo spazio per i commenti è un po 'troppo piccolo per ulteriori discussioni. Comunque:

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

non fornisce alcuna allocazione di memoria per l'array in questione.

Come accennato in precedenza, e come definito dalla norma, l'affermazione:

static uint8_t l_matrix[10][20];

ha riservato 200 posizioni sequenziali di tipo uint8_t.

Riferendosi a l_matrix utilizzando dichiarazioni del modulo:

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

ti darà il contenuto dell'elemento colno'th trovato nella riga rowno.

Tutte le manipolazioni del puntatore tengono automaticamente conto della dimensione dell'oggetto puntato. - K&R Sezione 5.4, p.103

Questo è anche il caso se qualsiasi spostamento del riempimento o dell'allineamento dei byte è coinvolto nella memorizzazione dell'oggetto a portata di mano. Il compilatore si adatterà automaticamente a questi. Per definizione dello standard ANSI C.

HTH

Saluti,


1
uint8_t (* matrix_ptr) [] [20] << le prime parentesi devono essere omesse, quella corretta è uint8_t (* matrix_ptr) [20]
Aconcagua

5

In C99 (supportato da clang e gcc) c'è una sintassi oscura per il passaggio di array multidimensionali alle funzioni per riferimento:

int l_matrix[10][20];

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

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

A differenza di un semplice puntatore, questo suggerisce la dimensione dell'array, consentendo teoricamente al compilatore di avvertire del passaggio di un array troppo piccolo e individuare un accesso ovvio fuori dai limiti.

Purtroppo, non si risolve sizeof()e i compilatori non sembrano ancora utilizzare tali informazioni, quindi rimane una curiosità.


1
Questa risposta è fuorviante: ciò non rende l'argomento un array di dimensioni fisse, è comunque un puntatore. static 10è una sorta di garanzia che siano presenti almeno 10 elementi, il che significa ancora una volta che la dimensione non è fissa.
bluss

1
@bluss la domanda riguardava un puntatore, quindi non vedo come rispondere con un puntatore (annotando per riferimento ) sia fuorviante. La matrice ha una dimensione fissa dal punto di vista della funzione, perché l'accesso agli elementi oltre questi limiti non è definito.
Kornel

Non penso che l'accesso oltre 10 sia indefinito, non vedo nulla che lo indichi.
bluss

Questa risposta sembra suggerire che senza la parola chiave static, l'array non sarebbe passato per riferimento, il che non è vero. Gli array vengono comunque passati per riferimento. La domanda originale era posta su un caso d'uso diverso: l'accesso a elementi dell'array 2D utilizzando un puntatore supplementare all'interno della stessa funzione / spazio dei nomi.
Palo

4

Puoi sempre evitare di giocherellare con il compilatore dichiarando l'array come lineare e facendo da solo il calcolo dell'indice (row, col) per array.

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

}

questo è quello che avrebbe fatto comunque il compilatore.


1
Questo è ciò che fa comunque il compilatore C. C non ha davvero alcuna nozione reale di un "array" - la notazione [] è solo zucchero sintattico per l'aritmetica dei puntatori
Ken Keenan,

7
Questa soluzione ha lo svantaggio di non trovare mai il modo giusto per farlo.
Craig McQueen,

2

La sintassi di base dell'inizializzazione del puntatore che punta a un array multidimensionale è

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

La sintassi di base per chiamarlo è

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

Ecco un esempio:

#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;
}

L'output è:

Subham
Messi

Ha funzionato...


1

Puoi farlo in questo modo:

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

1
non occupa 10 * 20 byte di ram? (im su un microcontrollore)
Dill

Occuperà 4 byte o qualsiasi dimensione sia grande un puntatore nella tua casella. Ma ricorda che se hai questo, devi indicizzare con matrix_ptr [0] [x] [y] o (* matrix_ptr) [x] [y]. È l'interpretazione diretta e parola per parola del "puntatore a un array bidimensionale": p
Johannes Schaub - litb

Grazie litb, ho dimenticato di menzionare come accedervi. Non ha senso modificare la mia risposta dato che hai fatto un ottimo lavoro con la tua risposta :)
Nick Dandoulakis

Quindi, questo occupa 10 * 20 byte di RAM o no?
Danijel

@Danijel, poiché è un puntatore a un array bidimensionale, occuperà solo 4 byte o qualsiasi dimensione sia un puntatore nella tua casella, ovvero 16 bit, 32 bit, 64 bit, ecc.
Nick Dandoulakis

1

Vuoi un puntatore al primo elemento, quindi;

static uint8_t l_matrix[10][20];

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

0

Puoi anche aggiungere un offset se desideri utilizzare indici negativi:

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

Se il tuo compilatore fornisce un errore o un avviso, potresti usare:

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

Ciao. Questa domanda è contrassegnata con c, quindi la risposta dovrebbe essere nella stessa lingua. Si prega di osservare i tag.
2501
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.