Эта идиома естественным образом выпадает из распределения одномерного массива. Начнем с выделения одномерного массива произвольного типа 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])
, что и, и в итоге мы выделяем непрерывный кусок памяти, достаточно большой, чтобы вместить массив N
by 10
by . 5
int
Итак, это сторона распределения; как насчет стороны доступа?
Помните, что []
операция над индексом определяется в терминах арифметики указателя: 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. Помните, что массивы не являются указателями - вместо этого выражения массива при необходимости преобразуются в выражения указателя.