Ten idiom naturalnie wypada z alokacji tablicy 1D. Zacznijmy od przydzielenia tablicy 1D dowolnego typu T
:
T *p = malloc( sizeof *p * N );
Proste, prawda? Wyrażenie *p
ma typ T
, więc sizeof *p
daje taki sam wynik jak sizeof (T)
, więc mamy wystarczająco dużo miejsca alokacji dla N
-elementowe tablicy T
. Dotyczy to każdego typuT
.
Teraz T
zastąpmy typem tablicowym, takim jak R [10]
. Wtedy staje się nasz przydział
R (*p)[10] = malloc( sizeof *p * N);
Semantyka tutaj jest dokładnie taka sama, jak w przypadku metody alokacji 1D; zmienił się tylko typ p
. Zamiast T *
tego jest teraz R (*)[10]
. Wyrażenie *p
ma typ, T
który jest typem R [10]
, więc sizeof *p
jest równoważne temu, sizeof (T)
które jest równoważne sizeof (R [10])
. Więc przydzielamy wystarczającą ilość miejsca na tablicę N
by 10
elementu R
.
Jeśli chcemy, możemy pójść jeszcze dalej; przypuśćmy, że R
jest typem tablicowym int [5]
. Zastąp to R
i otrzymamy
int (*p)[10][5] = malloc( sizeof *p * N);
Ta sama umowa - sizeof *p
jest taka sama jak sizeof (int [10][5])
i kończymy przydzielanie ciągłego kawałka pamięci wystarczająco dużej, aby pomieścić tablicę N
by 10
by . 5
int
Więc to jest strona alokacji; a co ze stroną dostępową?
Pamiętaj, że []
operacja z indeksem dolnym jest zdefiniowana w kategoriach arytmetyki wskaźnika: a[i]
jest zdefiniowana jako *(a + i)
1 . W ten []
sposób operator indeksu niejawnie odwołuje się do wskaźnika. Jeśli p
jest wskaźnikiem do T
, możesz uzyskać dostęp do wskazanej wartości albo przez jawne wyłuskiwanie za pomocą *
operatora jednoargumentowego :
T x = *p;
lub korzystając z []
operatora indeksu:
T x = p[0]; // identical to *p
Tak więc, jeśli p
wskazuje na pierwszy element tablicy , możesz uzyskać dostęp do dowolnego elementu tej tablicy, używając indeksu na wskaźniku 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
Teraz powtórzmy naszą operację podstawienia i zastąpmy T
typem tablicy 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];
Jedna natychmiast widoczna różnica; jawnie usuwamy odwołanie p
przed zastosowaniem operatora indeksu dolnego. Nie chcemy indeksować do p
, chcemy indeksować do tego, co p
wskazuje na (w tym przypadku tablicę arr[0]
). Ponieważ jednoargumentowy *
ma niższy priorytet niż []
operator indeksu dolnego , musimy użyć nawiasów, aby jawnie grupować p
z *
. Ale pamiętajcie z góry, że *p
to to samo co p[0]
, więc możemy to zastąpić
R x = (p[0])[i];
Lub tylko
R x = p[0][i];
Tak więc, jeśli p
wskazuje tablicę 2D, możemy indeksować do tej tablicy w następujący p
sposób:
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
Biorąc to do tych samych wniosków, jak powyżej, zastępując R
w 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];
Działa to tak samo, jeśli p
wskazuje na zwykłą tablicę lub jeśli wskazuje na przydzieloną pamięć malloc
.
Ten idiom ma następujące zalety:
- To proste - tylko jeden wiersz kodu, w przeciwieństwie do fragmentarycznej metody alokacji
T **arr = malloc( sizeof *arr * N );
if ( arr )
{
for ( size_t i = 0; i < N; i++ )
{
arr[i] = malloc( sizeof *arr[i] * M );
}
}
- Wszystkie wiersze przydzielonej tablicy są * ciągłe *, co nie ma miejsca w przypadku powyższej metody alokacji fragmentarycznej;
- Zwolnienie tablicy jest równie łatwe za pomocą pojedynczego wywołania funkcji
free
. Ponownie, nie jest to prawdą w przypadku fragmentarycznej metody alokacji, w której trzeba cofnąć przydział każdego z nich, arr[i]
zanim będzie można zwolnić arr
.
Czasami preferowana jest metoda alokacji fragmentarycznej, na przykład gdy sterta jest mocno pofragmentowana i nie można przydzielić pamięci jako ciągłego fragmentu lub chcesz przydzielić tablicę „postrzępioną”, w której każdy wiersz może mieć inną długość. Ale ogólnie jest to lepsza droga.
1. Pamiętaj, że tablice nie są wskaźnikami - zamiast tego wyrażenia tablicowe są w razie potrzeby konwertowane na wyrażenia wskaźnikowe.