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 *pfornisce lo stesso risultato di sizeof (T), quindi stiamo allocando spazio sufficiente per un Narray -element di T. Questo è vero per qualsiasi tipoT .
Ora sostituiamo Tcon 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 *pha tipo Tche è tipo R [10], quindi sizeof *pè equivalente a sizeof (T)quale è equivalente asizeof (R [10]) . Quindi stiamo allocando spazio sufficiente per un array Nper 10elemento di R.
Possiamo andare oltre se vogliamo; supponiamo che Rsia 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 Nby10 per 5schiera 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 ppunta 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 Tcon 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 pprima 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 pcon *. 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 ppunta a un array 2D, possiamo indicizzare in quell'array in questo pmodo:
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 Rcon 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 ppunta 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.