Эта идиома естественным образом выпадает из распределения одномерного массива. Начнем с выделения одномерного массива произвольного типа T:
T *p = malloc( sizeof *p * N );
Все просто, правда? Выражение *p имеет тип T, поэтому sizeof *pдает тот же результат , как и sizeof (T), таким образом , мы выделить достаточно места для Nэлементного массива T. Это верно для любого типаT .
Теперь давайте Tзаменим типом массива, например R [10]. Тогда наше распределение становится
R (*p)[10] = malloc( sizeof *p * N);
Семантика здесь точно такая же, как и у метода одномерного распределения; все, что изменилось, - это тип p. Вместо T *этого сейчас R (*)[10]. Выражение *pимеет тип, Tкоторый является типом R [10], поэтому sizeof *pэквивалентно sizeof (T)which is эквивалентно sizeof (R [10]). Таким образом , мы выделить достаточно места для Nпо 10элементу массива R.
Мы можем пойти еще дальше, если захотим; Предположим, что Rэто тип массива int [5]. Замените это на, Rи мы получим
int (*p)[10][5] = malloc( sizeof *p * N);
То же самое - sizeof *pто же самое sizeof (int [10][5]), что и, и в итоге мы выделяем непрерывный кусок памяти, достаточно большой, чтобы вместить массив Nby 10by . 5int
Итак, это сторона распределения; как насчет стороны доступа?
Помните, что []операция над индексом определяется в терминах арифметики указателя: a[i]определяется как *(a + i)1 . Таким образом, оператор нижнего индекса [] неявно разыменовывает указатель. Если pэто указатель на T, вы можете получить доступ к указанному значению либо путем явного разыменования с помощью унарного *оператора:
T x = *p;
или с помощью []оператора индекса:
T x = p[0]; // identical to *p
Таким образом, если pуказывает на первый элемент массива , вы можете получить доступ к любому элементу этого массива, используя индекс в указателе 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
Теперь давайте снова выполним нашу операцию подстановки и заменим Tтипом массива 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];
Одно сразу очевидное отличие; мы явно разыменовываем pперед применением оператора индекса. Мы не хотим индексировать p, мы хотим индексировать то, на что p указывает (в данном случае массив arr[0] ). Так как унарные *имеют более низкий приоритет , чем индекс []оператор, мы должны использовать круглые скобки , чтобы в явном виде группы pс *. Но помните, что сверху это *pто же самое p[0], поэтому мы можем заменить это на
R x = (p[0])[i];
или просто
R x = p[0][i];
Таким образом, если pуказывает на 2D-массив, мы можем проиндексировать этот массив pследующим образом:
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
Приняв это к тому же выводу, что и выше, и заменив Rна 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];
Это работает точно так же, если pуказывает на обычный массив или если указывает на выделенную память malloc.
Эта идиома имеет следующие преимущества:
- Это просто - всего одна строка кода, в отличие от метода поэтапного распределения.
T **arr = malloc( sizeof *arr * N );
if ( arr )
{
for ( size_t i = 0; i < N; i++ )
{
arr[i] = malloc( sizeof *arr[i] * M );
}
}
- Все строки выделенного массива являются * смежными *, чего нельзя сказать о методе частичного выделения выше;
- Освободить массив так же легко с помощью одного вызова
free. Опять же, это не так с методом частичного распределения, где вы должны освободить каждый, arr[i]прежде чем вы сможете освободить arr.
Иногда предпочтительнее использовать метод поэтапного распределения, например, когда ваша куча сильно фрагментирована и вы не можете выделить свою память как непрерывный фрагмент, или вы хотите выделить «зубчатый» массив, где каждая строка может иметь разную длину. Но в целом это лучший способ.
1. Помните, что массивы не являются указателями - вместо этого выражения массива при необходимости преобразуются в выражения указателя.