Questo idioma cade naturalmente fuori dall'allocazione di array 1D. Cominciamo con l'allocazione di un array 1D di un tipo arbitrario T
:
T *p = malloc( sizeof *p * N );
Semplice, vero? L' espressione *p
ha tipo T
, quindi sizeof *p
fornisce lo stesso risultato di sizeof (T)
, quindi stiamo allocando spazio sufficiente per un N
array -element di T
. Questo è vero per qualsiasi tipoT
.
Ora sostituiamo T
con un tipo di array come R [10]
. Allora la nostra allocazione diventa
R (*p)[10] = malloc( sizeof *p * N);
La semantica qui è esattamente la stessa del metodo di allocazione 1D; tutto ciò che è cambiato è il tipo di p
. Invece di T *
, è adesso R (*)[10]
. L'espressione *p
ha tipo T
che è tipo R [10]
, quindi sizeof *p
è equivalente a sizeof (T)
quale è equivalente asizeof (R [10])
. Quindi stiamo allocando spazio sufficiente per un array N
per 10
elemento di R
.
Possiamo andare oltre se vogliamo; supponiamo che R
sia esso stesso un tipo di matrice int [5]
. SostituisciloR
e otteniamo
int (*p)[10][5] = malloc( sizeof *p * N);
Stessa cosa - sizeof *p
è la stessa sizeof (int [10][5])
, e finiamo per l'assegnazione di un pezzo di memoria contiguo abbastanza grande da contenere un N
by10
per 5
schiera di int
.
Quindi questo è il lato dell'allocazione; e il lato di accesso?
Ricorda che l' []
operazione in pedice è definita in termini di aritmetica del puntatore: a[i]
è definita come *(a + i)
1 . Pertanto, l'operatore pedice dereferenzia []
implicitamente un puntatore. Se p
è un puntatore a T
, puoi accedere al valore a cui si punta o dereferenziando esplicitamente con l' *
operatore unario :
T x = *p;
o utilizzando l' []
operatore pedice:
T x = p[0]; // identical to *p
Pertanto, se p
punta al primo elemento di un array , puoi accedere a qualsiasi elemento di quell'array utilizzando un pedice sul puntatore 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
Ora, eseguiamo di nuovo la nostra operazione di sostituzione e sostituiamo T
con il tipo di matrice 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];
Una differenza immediatamente evidente; stiamo esplicitamente dereferenziando p
prima di applicare l'operatore pedice. Non vogliamo indicarci in p
, vogliamo indicarci cosa p
punta (in questo caso, l' array arr[0]
). Dal momento che unario *
ha la precedenza inferiore rispetto l'indice []
dell'operatore, dobbiamo usare le parentesi per esplicitamente gruppo p
con *
. Ma ricorda dall'alto che *p
è lo stesso di p[0]
, quindi possiamo sostituirlo con
R x = (p[0])[i];
o semplicemente
R x = p[0][i];
Pertanto, se p
punta a un array 2D, possiamo indicizzare in quell'array in questo p
modo:
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
Portando questo alla stessa conclusione di cui sopra e sostituendolo R
con 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];
Funziona allo stesso modo se p
punta a un array normale o se punta alla memoria allocata malloc
.
Questo idioma ha i seguenti vantaggi:
- È semplice: solo una riga di codice, al contrario del metodo di allocazione frammentario
T **arr = malloc( sizeof *arr * N );
if ( arr )
{
for ( size_t i = 0; i < N; i++ )
{
arr[i] = malloc( sizeof *arr[i] * M );
}
}
- Tutte le righe dell'array allocato sono * contigue *, il che non è il caso del metodo di allocazione frammentario sopra;
- La deallocazione dell'array è altrettanto semplice con una singola chiamata a
free
. Ancora una volta, non è vero con il metodo di allocazione frammentario, in cui devi deallocare ciascuno arr[i]
prima di poterlo deallocare arr
.
A volte il metodo di allocazione frammentario è preferibile, ad esempio quando il tuo heap è molto frammentato e non puoi allocare la tua memoria come un blocco contiguo, o vuoi allocare un array "frastagliato" in cui ogni riga può avere una lunghezza diversa. Ma in generale, questo è il modo migliore per andare.
1. Ricordare che gli array non sono puntatori, ma le espressioni di array vengono convertite in espressioni puntatore se necessario.