Причудливый способ выделения двумерного массива?
В проекте кто-то нажал эту строку:
double (*e)[n+1] = malloc((n+1) * sizeof(*e));
Что предположительно создает двумерный массив (n + 1) * (n + 1), удваивается.
Предположительно, я говорю, потому что до сих пор никто, кого я попросил, не мог сказать мне, что именно это делает, и откуда он возник или почему он должен работать (якобы это происходит, но я еще не покупаю его).
Возможно, мне не хватает чего-то очевидного, но я был бы признателен, если бы кто-нибудь мог объяснить мне выше строки. Потому что лично я бы чувствовал себя намного лучше, если бы использовал то, что мы действительно понимаем.
Ответы
Ответ 1
Переменная e
является указателем на массив элементов n + 1
типа double
.
Используя оператор разыменования на e
, вы получите базовый тип e
, который является "массивом элементов n + 1
типа double
".
Вызов malloc
просто берет базовый тип e
(объясняется выше) и получает его размер, умножает его на n + 1
и передает этот размер функции malloc
. По существу выделяя массив n + 1
массивов элементов n + 1
double
.
Ответ 2
Это типичный способ динамического размещения двумерных массивов.
-
e
- указатель массива на массив типа double [n+1]
.
-
sizeof(*e)
поэтому задает тип типа заостренного типа, размер которого составляет один массив double [n+1]
.
- Вы выделяете место для
n+1
таких массивов.
- Вы указали указатель массива
e
на первый массив в массиве массивов.
- Это позволяет использовать
e
как e[i][j]
для доступа к отдельным элементам в 2D-массиве.
Лично я считаю, что этот стиль намного проще читать:
double (*e)[n+1] = malloc( sizeof(double[n+1][n+1]) );
Ответ 3
Эта идиома естественно падает из распределения массива 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
.
Эта идиома имеет следующие преимущества:
Иногда метод поэтапного выделения предпочтительнее, например, когда ваша куча сильно фрагментирована, и вы не можете выделить свою память как непрерывный фрагмент, или вы хотите выделить "зубчатый" массив, где каждая строка может иметь разную длину, Но в целом, это лучший способ пойти.
1. Помните, что массивы не являются указателями - вместо этого выражения массива преобразуются в выражения указателя по мере необходимости.