Как мне работать с динамическими многомерными массивами в C? - PullRequest
58 голосов
/ 28 мая 2009

Кто-нибудь знает, как я могу использовать динамически распределенные многомерные массивы с помощью C? Это возможно?

Ответы [ 8 ]

74 голосов
/ 28 мая 2009

С динамическим размещением, используя malloc:

int** x;

x = malloc(dimension1_max * sizeof(int*));
for (int i = 0; i < dimension1_max; i++) {
  x[i] = malloc(dimension2_max * sizeof(int));
}

[...]

for (int i = 0; i < dimension1_max; i++) {
  free(x[i]);
}
free(x);

Это выделяет двумерный массив размером dimension1_max * dimension2_max. Так, например, если вы хотите массив 640 * 480 (например, пикселей изображения), используйте dimension1_max = 640, dimension2_max = 480. Затем вы можете получить доступ к массиву, используя x[d1][d2], где d1 = 0 ..639, d2 = 0..479.

Но поиск в SO или Google также показывает другие возможности, например в этом вопросе SO

Обратите внимание, что в этом случае ваш массив не будет выделять непрерывную область памяти (640 * 480 байт), что может вызвать проблемы с функциями, которые предполагают это. Таким образом, чтобы получить массив, удовлетворяющий условию, замените блок malloc, приведенный выше:

int** x;
int* temp;

x = malloc(dimension1_max * sizeof(int*));
temp = malloc(dimension1_max * dimension2_max * sizeof(int));
for (int i = 0; i < dimension1_max; i++) {
  x[i] = temp + (i * dimension2_max);
}

[...]

free(temp);
free(x);
73 голосов
/ 09 октября 2012

Начиная с C99, C имеет двумерные массивы с динамическими границами. Если вы хотите избежать размещения таких зверей в стеке (что вам следует), вы можете легко выделить их за один раз, как показано ниже:

double (*A)[n] = malloc(sizeof(double[n][n]));

и все. Затем вы можете легко использовать его, как для 2D-массивов, например, A[i][j]. И не забывайте, что один в конце

free(A);

Рэнди Мейерс написал серию статей, объясняющих массивы переменной длины (VLA) .

51 голосов
/ 28 мая 2009

Основы

Массивы в c объявляются и доступны с помощью оператора []. Так что

int ary1[5];

объявляет массив из 5 целых чисел. Элементы нумеруются с нуля, поэтому ary1[0] - первый элемент, а ary1[4] - последний элемент. Примечание 1: инициализация по умолчанию отсутствует, поэтому память, занятая массивом, может изначально содержать что угодно . Примечание 2: ary1[5] обращается к памяти в неопределенном состоянии (которое может быть вам даже недоступно), поэтому не делайте этого!

Многомерные массивы реализованы в виде массива массивов (массивов (из ...)). Так

float ary2[3][5];

объявляет массив из 3 одномерных массивов по 5 чисел с плавающей запятой каждый. Теперь ary2[0][0] - это первый элемент первого массива, ary2[0][4] - последний элемент первого массива, а ary2[2][4] - последний элемент последнего массива. Стандарт '89 требует, чтобы эти данные были смежными (с. A8.6.2 на стр. 216 моего K & R 2nd. Ed.), Но, похоже, не влияют на заполнение.

Попытка стать динамичной в более чем одном измерении

Если вы не знаете размер массива во время компиляции, вам нужно динамически распределить массив. Заманчиво попробовать

double *buf3;
buf3 = malloc(3*5*sizeof(double));
/* error checking goes here */

, который должен работать, если компилятор не дополняет распределение (вставьте дополнительное пространство между одномерными массивами). Может быть безопаснее идти с:

double *buf4;
buf4 = malloc(sizeof(double[3][5]));
/* error checking */

но в любом случае трюк приходит во время разыменования. Вы не можете написать buf[i][j], потому что buf имеет неправильный тип. Вы также не можете использовать

double **hdl4 = (double**)buf;
hdl4[2][3] = 0; /* Wrong! */

потому что компилятор ожидает, что hdl4 будет адресом двойного адреса. Вы также не можете использовать double incomplete_ary4[][];, потому что это ошибка;

Так что вы можете сделать?

  • Выполните арифметику строк и столбцов самостоятельно
  • Выделите и выполните работу в функции
  • Использовать массив указателей (механизм, о котором говорит qrdl)

Сделай математику самостоятельно

Просто вычислите смещение памяти для каждого элемента следующим образом:

  for (i=0; i<3; ++i){
     for(j=0; j<3; ++j){
        buf3[i * 5 + j] = someValue(i,j); /* Don't need to worry about 
                                             padding in this case */
     }
  }

Выделите и выполните работу в функции

Определите функцию, которая принимает необходимый размер в качестве аргумента и работает как обычно

void dary(int x, int y){
  double ary4[x][y];
  ary4[2][3] = 5;
}

Конечно, в этом случае ary4 является локальной переменной, и вы не можете ее вернуть: вся работа с массивом должна выполняться в функции, которую вы вызываете в функциях, которые it вызывает.

Массив указателей

Учтите это:

double **hdl5 = malloc(3*sizeof(double*));
/* Error checking */
for (i=0; i<3; ++i){
   hdl5[i] = malloc(5*sizeof(double))
   /* Error checking */
}

Теперь hdl5 указывает на массив указателей, каждый из которых указывает на массив двойных чисел. Круто то, что вы можете использовать двумерную запись массива для доступа к этой структуре --- hdl5[0][2] получает средний элемент первой строки --- но это, тем не менее, объект другого типа, чем двумерный массив, объявленный double ary[3][5];.

Эта структура более гибкая, чем двумерный массив (поскольку строки не обязательно должны быть одинаковой длины), но доступ к ней, как правило, будет медленнее и требует больше памяти (вам нужно место для хранения промежуточных указателей).

Обратите внимание, что, поскольку я не настроил охрану, вам придется самостоятельно следить за размером всех массивов.

арифметика

c не поддерживает векторную, матричную или тензорную математику, вам придется реализовать ее самостоятельно или принести библиотеку.

Умножение с помощью скейлера, а также сложение и вычитание массивов одинакового ранга очень просты: просто зацикливайте элементы и выполняйте операцию по мере необходимости. Внутренние продукты так же прямо вперед.

Наружные продукты означают больше петель.

9 голосов
/ 09 октября 2012

Если вы знаете количество столбцов во время компиляции, это довольно просто:

#define COLS ...
...
size_t rows;
// get number of rows
T (*ap)[COLS] = malloc(sizeof *ap * rows); // ap is a *pointer to an array* of T

Вы можете рассматривать ap как любой двумерный массив:

ap[i][j] = x;

Когда вы закончите, вы освобождаете его как

free(ap);

Если вы не знаете количество столбцов во время компиляции, но работаете с компилятором C99 или компилятором C2011, который поддерживает массивы переменной длины, это все еще довольно просто:

size_t rows;
size_t cols;
// get rows and cols
T (*ap)[cols] = malloc(sizeof *ap * rows);
...
ap[i][j] = x;
...
free(ap);

Если вы не знаете количество столбцов во время компиляции и работаете с версией C, которая не поддерживает массивы переменной длины, вам нужно будет сделать что-то другое. Если вам нужно, чтобы все элементы были размещены в непрерывном фрагменте (например, в обычном массиве), то вы можете выделить память как одномерный массив и вычислить смещение 1D:

size_t rows, cols;
// get rows and columns
T *ap = malloc(sizeof *ap * rows * cols);
...
ap[i * rows + j] = x;
...
free(ap);

Если вам не нужна непрерывная память, вы можете воспользоваться двухэтапным методом выделения:

size_t rows, cols;
// get rows and cols
T **ap = malloc(sizeof *ap * rows);
if (ap)
{
  size_t i = 0;
  for (i = 0; i < cols; i++)
  {
    ap[i] = malloc(sizeof *ap[i] * cols);
  }
}

ap[i][j] = x;

Поскольку распределение было двухэтапным процессом, освобождение также должно быть двухэтапным процессом:

for (i = 0; i < cols; i++)
  free(ap[i]);
free(ap);
0 голосов
/ 01 августа 2017

Вот рабочий код, который определяет подпрограмму make_3d_array для выделения многомерного трехмерного массива с элементами N1, N2 и N3 в каждом измерении, а затем заполняет его случайными числами. Вы можете использовать обозначение A[i][j][k] для доступа к его элементам.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>


// Method to allocate a 2D array of floats
float*** make_3d_array(int nx, int ny, int nz) {
    float*** arr;
    int i,j;

    arr = (float ***) malloc(nx*sizeof(float**));

    for (i = 0; i < nx; i++) {
        arr[i] = (float **) malloc(ny*sizeof(float*));

        for(j = 0; j < ny; j++) {
            arr[i][j] = (float *) malloc(nz * sizeof(float));
        }
    }

    return arr;
} 



int main(int argc, char *argv[])
{
    int i, j, k;
    size_t N1=10,N2=20,N3=5;

    // allocates 3D array
    float ***ran = make_3d_array(N1, N2, N3);

    // initialize pseudo-random number generator
    srand(time(NULL)); 

    // populates the array with random numbers
    for (i = 0; i < N1; i++){
        for (j=0; j<N2; j++) {
            for (k=0; k<N3; k++) {
                ran[i][j][k] = ((float)rand()/(float)(RAND_MAX));
            }
        }
   }

    // prints values
    for (i=0; i<N1; i++) {
        for (j=0; j<N2; j++) {
            for (k=0; k<N3; k++) {
                printf("A[%d][%d][%d] = %f \n", i,j,k,ran[i][j][k]);
            }
        }
    }

    free(ran);
}
0 голосов
/ 09 октября 2012
int rows, columns;
/* initialize rows and columns to the desired value */

    arr = (int**)malloc(rows*sizeof(int*));
        for(i=0;i<rows;i++)
        {
            arr[i] = (int*)malloc(cols*sizeof(int));
        }
0 голосов
/ 09 октября 2012

Нет способа выделить все это за один раз. Вместо этого создайте массив указателей, затем для каждого указателя создайте для него память. Например:

int** array;
array = (int**)malloc(sizeof(int*) * 50);
for(int i = 0; i < 50; i++)
    array[i] = (int*)malloc(sizeof(int) * 50);

Конечно, вы также можете объявить массив как int* array[50] и пропустить первый malloc, но второй набор необходим для динамического выделения требуемого хранилища.

Можно взломать способ выделить его за один шаг, но это потребует пользовательской функции поиска, но запись о том, что он всегда будет работать, может раздражать. Примером может быть L(arr,x,y,max_x) arr[(y)*(max_x) + (x)], затем malloc блок 50 * 50 дюймов или что-то еще и доступ с использованием этого макроса L, например,

#define L(arr,x,y,max_x) arr[(y)*(max_x) + (x)]

int dim_x = 50;
int dim_y = 50;

int* array = malloc(dim_x*dim_y*sizeof(int));

int foo = L(array, 4, 6, dim_x);

Но это намного хуже, если вы не знаете, что вы делаете с макросом препроцессора.

0 голосов
/ 09 октября 2012

Маллок подойдет.

 int rows = 20;
 int cols = 20;
 int *array;

  array = malloc(rows * cols * sizeof(int));

Обратитесь за помощью к следующей статье: -

http://courses.cs.vt.edu/~cs2704/spring00/mcquain/Notes/4up/Managing2DArrays.pdf

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...