Ця ідіома природно випадає з розподілу 1D-масиву. Почнемо з виділення 1D масиву якогось довільного типу 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);
Семантика тут точно така ж, як метод 1D-розподілу; все, що змінилося, - це тип p. Замість цього T *- це зараз R (*)[10]. Вираз *pмає тип, Tякий є типом R [10], тому sizeof *pеквівалентний тому, sizeof (T)що еквівалентно 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допомогою з 10допомогою 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. Пам'ятайте, що масиви не є покажчиками - натомість вирази масивів перетворюються на вирази вказівників, якщо це необхідно.