В проекте кто-то выдвинул эту строку:
double (*e)[n+1] = malloc((n+1) * sizeof(*e));
Что предположительно создает двумерный массив из (n + 1) * (n + 1) удвоений.
Предположительно , я говорю, потому что до сих пор ни один из тех, кого я спрашивал, не мог сказать мне, что именно это делает, ни откуда оно взялось, ни почему оно должно работать (что якобы работает, но я еще не купился на это).
Возможно, мне не хватает чего-то очевидного, но я был бы признателен, если бы кто-нибудь объяснил мне приведенную выше строку. Потому что лично мне было бы намного лучше, если бы мы использовали то, что действительно понимаем.
c
arrays
multidimensional-array
malloc
allocation
Пользователь1291
источник
источник
n+1
а вместо этогоdouble (*e)[rows] = malloc(columns * sizeof *e);
Ответы:
Переменная
e
представляет собой указатель на массивn + 1
элементов типаdouble
.Использование оператора разыменования
e
дает вам базовый тип,e
который является «массивомn + 1
элементов типаdouble
».malloc
Вызов просто берет базовый-типe
(описано выше) и получает его размер, умножает егоn + 1
, и передавая этот размер кmalloc
функции. По сути, выделяя массивn + 1
массивовn + 1
элементовdouble
.источник
sizeof(*e)
эквивалентенsizeof(double [n + 1])
. Умножьте это на,n + 1
и вы получите достаточно.n+1
оба измерения, результат будет квадратным. Если вы это сделаетеdouble (*e)[cols] = malloc(rows * sizeof(*e));
, результат будет иметь любое указанное вами количество строк и столбцов.int rows = n+1
иint cols = n+1
. Боже, спаси нас от хитрого кода.Это типичный способ динамического размещения 2D-массивов.
e
- указатель на массив типаdouble [n+1]
.sizeof(*e)
поэтому дает тип указанного типа, который является размером одногоdouble [n+1]
массива.n+1
таких массивов.e
чтобы он указывал на первый массив в этом массиве массивов.e
ase[i][j]
для доступа к отдельным элементам в 2D-массиве.Лично я считаю, что этот стиль легче читать:
источник
ptr = malloc(sizeof *ptr * count)
.malloc(row*col*sizeof(double))
возникает приrow*col*sizeof()
переполнении, но этого неsizeof()*row*col
происходит. (например, row, col areint
)sizeof *e * (n+1)
проще поддерживать; если вы когда - нибудь решите изменить базовый тип (отdouble
доlong double
, к примеру), то вам нужно всего лишь изменить декларациюe
; вам не нужно изменятьsizeof
выражение вmalloc
вызове (что экономит время и защищает вас от изменения его в одном месте, но не в другом).sizeof *e
всегда даст вам нужный размер.Эта идиома естественным образом выпадает из распределения одномерного массива. Начнем с выделения одномерного массива произвольного типа
T
:Все просто, правда? Выражение
*p
имеет типT
, поэтомуsizeof *p
дает тот же результат , как иsizeof (T)
, таким образом , мы выделить достаточно места дляN
элементного массиваT
. Это верно для любого типаT
.Теперь давайте
T
заменим типом массива, напримерR [10]
. Тогда наше распределение становитсяСемантика здесь точно такая же, как и у метода одномерного распределения; все, что изменилось, - это тип
p
. ВместоT *
этого сейчасR (*)[10]
. Выражение*p
имеет тип,T
который является типомR [10]
, поэтомуsizeof *p
эквивалентноsizeof (T)
which is эквивалентноsizeof (R [10])
. Таким образом , мы выделить достаточно места дляN
по10
элементу массиваR
.Мы можем пойти еще дальше, если захотим; Предположим, что
R
это тип массиваint [5]
. Замените это на,R
и мы получимТо же самое -
sizeof *p
то же самоеsizeof (int [10][5])
, что и, и в итоге мы выделяем непрерывный кусок памяти, достаточно большой, чтобы вместить массивN
by10
by .5
int
Итак, это сторона распределения; как насчет стороны доступа?
Помните, что
[]
операция над индексом определяется в терминах арифметики указателя:a[i]
определяется как*(a + i)
1 . Таким образом, оператор нижнего индекса[]
неявно разыменовывает указатель. Еслиp
это указатель наT
, вы можете получить доступ к указанному значению либо путем явного разыменования с помощью унарного*
оператора:или с помощью
[]
оператора индекса:Таким образом, если
p
указывает на первый элемент массива , вы можете получить доступ к любому элементу этого массива, используя индекс в указателеp
:Теперь давайте снова выполним нашу операцию подстановки и заменим
T
типом массиваR [10]
:Одно сразу очевидное отличие; мы явно разыменовываем
p
перед применением оператора индекса. Мы не хотим индексироватьp
, мы хотим индексировать то, на чтоp
указывает (в данном случае массивarr[0]
). Так как унарные*
имеют более низкий приоритет , чем индекс[]
оператор, мы должны использовать круглые скобки , чтобы в явном виде группыp
с*
. Но помните, что сверху это*p
то же самоеp[0]
, поэтому мы можем заменить это наили просто
Таким образом, если
p
указывает на 2D-массив, мы можем проиндексировать этот массивp
следующим образом:Приняв это к тому же выводу, что и выше, и заменив
R
наint [5]
:Это работает точно так же, если
p
указывает на обычный массив или если указывает на выделенную памятьmalloc
.Эта идиома имеет следующие преимущества:
free
. Опять же, это не так с методом частичного распределения, где вы должны освободить каждый,arr[i]
прежде чем вы сможете освободитьarr
.Иногда предпочтительнее использовать метод поэтапного распределения, например, когда ваша куча сильно фрагментирована и вы не можете выделить свою память как непрерывный фрагмент, или вы хотите выделить «зубчатый» массив, где каждая строка может иметь разную длину. Но в целом это лучший способ.
1. Помните, что массивы не являются указателями - вместо этого выражения массива при необходимости преобразуются в выражения указателя.
источник