динамически распределяемый массив массивов в C - PullRequest
17 голосов
/ 19 января 2009

Я не совсем понимаю некоторые базовые вещи в C, такие как динамическое размещение массивов массивов. Я знаю, что вы можете сделать:

int **m;

для объявления двумерного массива (который впоследствии будет выделен с использованием некоторой функции * alloc). Также к нему можно «легко» получить доступ, выполнив *(*(m + line) + column). Но как мне присвоить значение элементу из этого массива? При использовании gcc следующий оператор m[line][column] = 12; завершается с ошибкой сегментации.

Любые статьи / документы будут оценены. : -)

Ответы [ 7 ]

34 голосов
/ 19 января 2009

Синтаксис m[line][column] = 12 в порядке (при условии, что line и column находятся в диапазоне).

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

m = (int**)malloc(nlines * sizeof(int*));

for(i = 0; i < nlines; i++)
  m[i] = (int*)malloc(ncolumns * sizeof(int));

Некоторые примечания:

  • Таким образом, вы можете выделить каждую строку разной длины (например, треугольный массив)
  • Вы можете использовать realloc () или free () для отдельной строки позже при использовании массива
  • Вы должны освобождать () каждую строку, когда освобождаете () весь массив
5 голосов
/ 19 января 2009

Ваш синтаксис m [line] [colummn] правильный. Но чтобы использовать 2D-массив в C, вы должны выделить для него память. Например, этот код выделит память для таблицы заданной строки и столбца.

int** AllocateArray(int line, int column) {
  int** pArray = (int**)malloc(line*sizeof(int*));
  for ( int i = 0; i < line; i++ ) {
    pArray[i] = (int*)malloc(column*sizeof(int));
  }
  return pArray;
}

Обратите внимание, для краткости я пропустил проверки ошибок для malloc. Реальное решение должно включать их.

3 голосов
/ 19 января 2009

Это не двумерный массив - это массив массивов, поэтому ему нужно многократное распределение.

2 голосов
/ 19 января 2009

Вот модифицированная версия решения quinmars , которое выделяет только один блок памяти и может использоваться с общими значениями благодаря void *:

#include <stdlib.h>
#include <string.h>
#include <assert.h>

void ** array2d(size_t rows, size_t cols, size_t value_size)
{
    size_t index_size = sizeof(void *) * rows;
    size_t store_size = value_size * rows * cols;

    char * a = malloc(index_size + store_size);
    if(!a) return NULL;

    memset(a + index_size, 0, store_size);
    for(size_t i = 0; i < rows; ++i)
        ((void **)a)[i] = a + index_size + i * cols * value_size;

    return (void **)a;
}

int printf(const char *, ...);

int main()
{
    int ** a = (int **)array2d(5, 5, sizeof(int));
    assert(a);
    a[4][3] = 42;
    printf("%i\n", a[4][3]);
    free(a);
    return 0;
}

Я не уверен, действительно ли безопасно приводить void ** к int ** (я думаю, стандарт допускает преобразования при преобразовании в / из void *?), Но он работает в gcc. Чтобы быть в безопасности, вы должны заменить каждый случай void * на int * ...


Следующие макросы реализуют типобезопасную версию предыдущего алгоритма:

#define alloc_array2d(TYPE, ROWS, COLS) \
    calloc(sizeof(TYPE *) * ROWS + sizeof(TYPE) * ROWS * COLS, 1)

#define init_array2d(ARRAY, TYPE, ROWS, COLS) \
    do { for(int i = 0; i < ROWS; ++i) \
        ARRAY[i] = (TYPE *)(((char *)ARRAY) + sizeof(TYPE *) * ROWS + \
        i * COLS * sizeof(TYPE)); } while(0)

Используйте их так:

int ** a = alloc_array2d(int, 5, 5);
init_array2d(a, int, 5, 5);
a[4][3] = 42;
1 голос
/ 19 января 2009

Хамм. Как насчет старого модного дыма и зеркал в качестве опции?

#define ROWS  5
#define COLS 13
#define X(R, C) *(p + ((R) * ROWS) + (C))

int main(void)
{
    int *p = (int *) malloc (ROWS * COLS * sizeof(int));
    if (p != NULL)
    {
        size_t r;
        size_t c;
        for (r = 0; r < ROWS; r++)
        {
            for (c = 0; c < COLS; c++)
            {
                 X(r,c) = r * c;  /* put some silly value in that position */ 
            }
        }

        /* Then show the contents of the array */ 
        for (r = 0; r < ROWS; r++)
        {
            printf("%d ", r);   /* Show the row number */ 

            for (c = 0; c < COLS; c++)
            {
                 printf("%d", X(r,c));
            }

            printf("\n");
        }

        free(p);
    }
    else
    {
        /* issue some silly error message */ 
    }

    return 0;
}
1 голос
/ 19 января 2009

Хотя я согласен с другими ответами, в большинстве случаев лучше выделять весь массив сразу, потому что malloc довольно медленный.


int **
array_new(size_t rows, size_t cols)
{
    int **array2d, **end, **cur;
    int *array;

    cur = array2d = malloc(rows * sizeof(int *));
    if (!array2d)
        return NULL;

    array = malloc(rows * cols * sizeof(int));
    if (!array)
    {
        free(array2d);
        return NULL;
    }

    end = array2d + rows;
    while (cur != end)
    {
        *cur = array;
        array += cols;
        cur++;
    }

    return array2d;
}

Чтобы освободить массив, просто выполните: free(*array); free(array);

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

0 голосов
/ 19 января 2009

Использование malloc(3) для выделения первого массива и помещения туда указателей, созданных malloc(3), должно работать с array[r][c], поскольку оно должно быть эквивалентно *(*(array + r) + c), это в стандарте C.

...