Как правильно настроить, получить доступ и получить многомерный массив в C?

Я видел десятки вопросов о том, "что не так с моим кодом" в отношении многомерных массивов в C. По какой-то причине люди не могут обернуться вокруг того, что здесь происходит, поэтому я решил ответить на этот вопрос как ссылку на другие:

Как правильно настроить, получить доступ и получить многомерный массив в C?

Если у других есть полезные советы, пожалуйста, не стесняйтесь публиковать сообщения!

Ответ 1

В C с C99 даже динамические многомерные массивы можно легко выделить за один раз с помощью malloc и освободить с помощью free:

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

for (size_t i = 0; i < n; ++i)
  for (size_t j = 0; j < n; ++j)
      A[i][j] = someinvolvedfunction(i, j);

free(A);

Ответ 2

Существует как минимум четыре разных способа создания или моделирования многомерного массива на C89.

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

Один означает "выделить многомерный массив". Кажется, это нравится:

int (*rows)[NUM_ROWS][NUM_COLS] = malloc(sizeof *rows);
...
free(rows);

Тогда синтаксис доступа к элементу [i, j] равен (*rows)[i][j]. В C89, как NUM_COLS, так и NUM_ROWS должны быть известны во время компиляции. Это истинный 2-D массив, а rows - указатель на него.

Один - это "выделить массив строк". Это выглядит так:

int (*rows)[NUM_COLS] = malloc(sizeof(*rows) * NUM_ROWS);
...
free(rows);

Тогда синтаксис для доступа к элементу [i, j] равен rows[i][j]. В C89, NUM_COLS должен быть известен во время компиляции. Это настоящий 2-мерный массив.

Один из них: "выделите 1-мерный массив и притворитесь". Это выглядит так:

int *matrix = malloc(sizeof(int) * NUM_COLS * NUM_ROWS);
...
free(matrix);

Тогда синтаксис для доступа к элементу [i, j] равен matrix[NUM_COLS * i + j]. Это (конечно) не является истинным 2-мерным массивом. На практике он имеет тот же макет, что и один.

Ответ 3

Статически говоря, это легко понять:

int mtx[3][2] = {{1, 2},
                 {2, 3},
                 {3, 4}};

Ничего сложного здесь. 3 строки, 2 столбца; данные в колонке 1: 1, 2, 3; данные в колонке 2: 2, 3, 4. Мы можем получить доступ к элементам через одну и ту же конструкцию:

for(i = 0; i<3; i++){
    for(j = 0; j<2; j++)
        printf("%d ", mtx[i][j]);
    printf("\n");
}
//output
//1 2
//2 3
//3 4

Теперь рассмотрим это с помощью Указателей:

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

int row[2] = {1,2};

И знаешь что? Мы можем получить доступ к нему точно так же, как указатель.

printf("%d, %d\n",*row,*(row+1));   //prints 1, 2
printf("%d, %d\n",row[0],row[1]);   //prints 1, 2

Теперь, если мы не знаем количество значений в строке, мы можем сделать этот массив динамической длиной, если у нас есть указатель на int, и мы даем ему некоторую память:

int *row = malloc(X * sizeof(int));  //allow for X number of ints
*row = 1;        //row[0] = 1
*(row+1) = 2; //row[1] = 2
…
*(row+(X-1)) = Y; // row[x-1] = Some value y

Итак, теперь у нас есть динамический 1-мерный массив; один ряд. Но нам нужно много строк, а не только одно, и мы не знаем, сколько. Это означает, что нам нужен еще один динамический 1-мерный массив, каждый элемент которого будет указателем, указывающим на строку.

//we want enough memory to point to X number of rows
//each value stored there is a pointer to an integer
int ** matrix = malloc(X * sizeof(int *));

//conceptually:
(ptr to ptr to int)     (pointer to int)
   **matrix ------------> *row1 --------> [1][2]
                          *row2 --------> [2][3]
                          *row3 --------> [3][4]

Теперь все, что осталось сделать, это написать код, который будет выполнять эти динамические распределения:

int i, j, value = 0;

//allocate memory for the pointers to rows
int ** matrix = malloc(Rows * sizeof(int*));

//each row needs a dynamic number of elements
for(i=0; i<Rows; i++){
    // so we need memory for the number of items in each row… 
    // we could call this number of columns as well
    *(matrix + i) = malloc(X * sizeof(int));

     //While we’re in here, if we have the items we can populate the matrix
    for(j=0; j<X; j++)
        *(*(matrix+i)+j) = value; // if you deference (matrix + i) you get the row
                                  // if you add the column and deference again, you
                                  // get the actual item to store (not a pointer!)
}

Одна из самых важных вещей, которые нужно сделать сейчас - убедиться, что мы освобождаем память, когда это было сделано. Каждый уровень malloc() должен иметь одинаковое количество вызовов free(), а вызовы должны быть в порядке FILO (обратном вызове malloc):

for(i=0; i<Rows; i++) 
    free(*(matrix + i));
free(matrix);

//set to NULL to clean up, matrix points to allocated memory now so let’s not use it!
matrix = NULL; 

Ответ 4

Если вы хотите использовать массив typedef'd, это еще проще.

Скажите, что у вас есть код typedef int LabeledAdjMatrix[SIZE][SIZE];

Затем вы можете использовать:

LabeledAdjMatrix *pMatrix = malloc(sizeof(LabeledAdjMatrix));

Затем вы можете написать:

for (i=0; i<SIZE; i++) {
    for (j=0; j<SIZE; j++) (*parr)[i][j] = k++; /* or parr[0][i][j]... */
}

Поскольку pArr является указателем на вашу матрицу, а * имеет более низкий приоритет, чем [];

Вот почему обычная идиома режима - это typedef строку:

typedef int LabeledAdjRow[SIZE];

Затем вы можете написать:

LabeledAdjRow *pMatrix = malloc(sizeof(LabeledAdjRow) * SIZE);
for (i=0; i<SIZE; i++) {
    for (j=0; j<SIZE; j++) parr[i][j] = k++;
}

И вы можете memcpy все, что непосредственно:

LabeledAdjRow *pOther = malloc(sizeof(LabeledAdjRow) * SIZE);
memcpy(pOther, pMatrix, sizeof(LabeledAdjRow) * SIZE);

Ответ 5

Начиная с ответа Джен, если вы хотите выделить пространство для трехмерного массива, таким же образом, вы можете сделать

int(*A)[n][n] = malloc(sizeof(int[num_of_2D_arrays][n][n]));

for (size_t p = 0; p < num_of_2D_arrays; p++)
  for (size_t i = 0; i < n; i++)
    for (size_t j = 0; j < n; j++)
      A[p][i][j] = p;

for (size_t p = 0; p < num_of_2D_arrays; p++)
    printf("Outter set %lu\n", p);
    for (size_t i = 0; i < n; i++)
      for (size_t j = 0; j < n; j++)
        printf(" %d", A[p][i][j]);
      printf("\n");

free(A);