Просто вычислите общий объем памяти, необходимый как для nrows
указателей строк, так и для фактических данных, сложите все это и выполните один вызов:
int **array = malloc(nrows * sizeof *array + (nrows * (ncolumns * sizeof **array));
Если вы думаете, что это выглядитслишком сложный, вы можете разделить его и сделать его немного самодокументированным, назвав различные термины выражения размера:
int **array; /* Declare this first so we can use it with sizeof. */
const size_t row_pointers_bytes = nrows * sizeof *array;
const size_t row_elements_bytes = ncolumns * sizeof **array;
array = malloc(row_pointers_bytes + nrows * row_elements_bytes);
Затем вам нужно пройти и инициализировать указатели строк так, чтобы каждая строкауказатель указывает на первый элемент для этой конкретной строки:
size_t i;
int * const data = array + nrows;
for(i = 0; i < nrows; i++)
array[i] = data + i * ncolumns;
Обратите внимание, что результирующая структура несколько отличается от того, что вы получите, например, int array[nrows][ncolumns]
, потому что у нас есть явные указатели строк, что означает, что длядля массива, выделенного следующим образом, нет реального требования, чтобы все строки имели одинаковое количество столбцов.
Это также означает, что такой доступ, как array[2][3]
, делает нечто отличное от аналогичного доступа в реальном 2d-массиве.,В этом случае самый внутренний доступ происходит первым, и array[2]
считывает указатель с 3-го элемента в array
.Этот указатель затем обрабатывается как основание массива (столбца), в который мы индексируем, чтобы получить четвертый элемент.
Напротив, для чего-то вроде
int array2[4][3];
, который является "упакованный "правильный 2d массив, занимающий всего 12 целых чисел, доступ, такой как array[3][2]
, просто разбивается на добавление смещения к базовому адресу, чтобы получить элемент.