Malloc 3-мерный массив в C? - PullRequest
       34

Malloc 3-мерный массив в C?

28 голосов
/ 21 февраля 2010

Я перевожу некоторый код MATLAB на C, и скрипт, который я конвертирую, интенсивно использует трехмерные массивы с 10 * 100 * 300 сложных записей. Размер массива также зависит от входа датчика, в идеале массив должен выделяться динамически. До сих пор я пробовал два подхода, первый из которых был плоским 1D массивом по линии

value = array[x + (y*xSize) + (z*ySize*xSize)]

Что вредит моему мозгу. Я также пробовал массив массивов указателей

int main () {
  int ***array = malloc(3*sizeof(int**));
  int i, j;

  for (i = 0; i < 3; i++) {
    *array[i] = malloc(3*sizeof(int*));
    for (j = 0; j < 3; j++) {
      array[i][j] = malloc(3*sizeof(int));
    }
  }

  array[1][2][1] = 10;

  return 0;
}

Что выдает ошибку сегмента при попытке назначить данные.

В идеальном мире я хотел бы использовать второй метод с обозначением массива для более чистого и простого программирования. Есть ли лучший способ динамически выделить трехмерный массив в C?

Ответы [ 13 ]

18 голосов
/ 21 февраля 2010

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

Если вам нужен правильный доступ к элементу массива, я бы написал вспомогательный метод для преобразования положений x, y, z в смещение в массив 1D

int offset(int x, int y, int z) { 
    return (z * xSize * ySize) + (y * xSize) + x; 
}
9 голосов
/ 21 февраля 2010

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

Сначала я определяю функцию free_data(), которая освобождает int *** с xlen и ylen в качестве первых двух размерных размеров. Нам не нужен параметр zlen, так как free() не принимает длину освобождаемого указателя.

void free_data(int ***data, size_t xlen, size_t ylen)
{
    size_t i, j;

    for (i=0; i < xlen; ++i) {
        if (data[i] != NULL) {
            for (j=0; j < ylen; ++j)
                free(data[i][j]);
            free(data[i]);
        }
    }
    free(data);
}

Функция перебирает указатель data, обнаруживает указатель i th int ** data[i]. Затем для данного указателя int ** он зацикливается на нем, обнаруживая j th int * в data[i][j], и освобождает его. Он также должен освободить data[i], как только освободит все data[i][j], и, наконец, он должен освободить data сам.

Теперь к функции распределения. Функция немного усложняется проверкой ошибок. В частности, поскольку существует 1 + xlen + xlen*ylen malloc вызовов, мы должны быть в состоянии обработать сбой в любом из этих вызовов и освободить всю память, выделенную нами до сих пор. Чтобы упростить задачу, мы полагаемся на тот факт, что free(NULL) не используется, поэтому мы устанавливаем все указатели на заданном уровне, равном NULL, прежде чем пытаться их распределить, чтобы в случае возникновения ошибки мы могли освободить все указатели.

Кроме этого, функция достаточно проста. Сначала мы выделяем пространство для xlen int ** значений, затем для каждого из этих xlen указателей мы выделяем пространство для ylen int * значений, а затем для каждого из этих xlen*ylen указателей мы выделяем пространство для zlen int значений, что дает нам общее пространство для xlen*ylen*zlen int значений:

int ***alloc_data(size_t xlen, size_t ylen, size_t zlen)
{
    int ***p;
    size_t i, j;

    if ((p = malloc(xlen * sizeof *p)) == NULL) {
        perror("malloc 1");
        return NULL;
    }

    for (i=0; i < xlen; ++i)
        p[i] = NULL;

    for (i=0; i < xlen; ++i)
        if ((p[i] = malloc(ylen * sizeof *p[i])) == NULL) {
            perror("malloc 2");
            free_data(p, xlen, ylen);
            return NULL;
        }

    for (i=0; i < xlen; ++i)
        for (j=0; j < ylen; ++j)
            p[i][j] = NULL;

    for (i=0; i < xlen; ++i)
        for (j=0; j < ylen; ++j)
            if ((p[i][j] = malloc(zlen * sizeof *p[i][j])) == NULL) {
                perror("malloc 3");
                free_data(p, xlen, ylen);
                return NULL;
            }

    return p;
}

Обратите внимание, что я немного упростил вызовы malloc: в общем случае вы не должны приводить возвращаемое значение malloc, а вместо этого указывать объект, для которого вы выделяете, в качестве операнда для оператора sizeof своего типа. Это делает malloc звонков проще для написания и менее подвержено ошибкам. Вам нужно включить stdlib.h для malloc.

Вот тестовая программа, использующая две вышеуказанные функции:

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

int main(void)
{
    int ***data;
    size_t xlen = 10;
    size_t ylen = 100;
    size_t zlen = 300;
    size_t i, j, k;

    srand((unsigned int)time(NULL));
    if ((data = alloc_data(xlen, ylen, zlen)) == NULL)
        return EXIT_FAILURE;

    for (i=0; i < xlen; ++i)
        for (j=0; j < ylen; ++j)
            for (k=0; k < zlen; ++k)
                data[i][j][k] = rand();

    printf("%d\n", data[1][2][1]);
    free_data(data, xlen, ylen);
    return EXIT_SUCCESS;
}

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

8 голосов
/ 02 декабря 2012

Вы уверены, что вам нужно использовать malloc? C позволяет создавать многомерные массивы изначально:

int a2[57][13][7];

Или вы можете использовать malloc следующим образом:

int (*a)[13][7]; // imitates 3d array with unset 3rd dimension
                 // actually it is a pointer to 2d arrays

a = malloc(57 * sizeof *a);    // allocates 57 rows

a[35][7][3] = 12; // accessing element is conventional

free(a); // freeing memory
7 голосов
/ 21 февраля 2010

В C89 нет способа сделать то, что вы хотите, потому что тип массива в C может быть указан только с известными значениями времени компиляции. Таким образом, чтобы избежать безумного динамического распределения, вам придется придерживаться одномерного способа. Вы можете использовать функцию, чтобы облегчить этот процесс

int index(int x, int y, int z) {
  return x + (y*xSize) + (z*ySize*xSize);
}

int value = array[index(a, b, c)];

В C99 вы можете использовать обычный синтаксис массива, даже если измерения являются значениями времени выполнения:

int (*array)[X][Y][Z] = (int(*)[X][Y][Z])malloc(sizeof *p); 
// fill...
int value = (*array)[a][b][c];

Однако он работает только с локальными нестатическими массивами.

6 голосов
/ 21 февраля 2010

О, я ненавижу распределение массивов malloc ^^

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

int main () {
  int ***array = (int***)malloc(3*sizeof(int**));
  int i, j;

  for (i = 0; i < 3; i++) {
    // Assign to array[i], not *array[i] (that would dereference an uninitialized pointer)
    array[i] = (int**)malloc(3*sizeof(int*));
    for (j = 0; j < 3; j++) {
      array[i][j] = (int*)malloc(3*sizeof(int));
    }
  }

  array[1][2][1] = 10;

  return 0;
}
2 голосов
/ 23 марта 2018

О Сегфо, Я уверен, что кто-то еще указал на это, но на всякий случай, в первой строке первой строки for добавлен '*'

for (i = 0; i < 3; i++) {
    *array[i] = malloc(3*sizeof(int*));
//  ^ we dont want to deference array twice
    for (j = 0; j < 3; j++) {
        array[i][j] = malloc(3*sizeof(int));
    }
}

попробуйте следующее:

    for (i = 0; i < 3; i++) {
        array[i] = malloc(3*sizeof(int*));
        for (j = 0; j < 3; j++) {
            array[i][j] = malloc(3*sizeof(int));
        }
    }
2 голосов
/ 09 июля 2012

Таким образом, вы можете выделить только 1 блок памяти, и динамический массив будет вести себя как статический (то есть, то же самое смежность памяти).Вы также можете освободить память с помощью одного свободного (массива), как обычные одномерные массивы.

double*** arr3dAlloc(const int ind1, const int ind2, const int ind3)
{
  int i;
  int j;
  double*** array = (double***) malloc( (ind1 * sizeof(double*)) + (ind1*ind2 * sizeof(double**)) + (ind1*ind2*ind3 * sizeof(double)) );
  for(i = 0; i < ind1; ++i) {
    array[i] = (double**)(array + ind1) + i * ind2;
    for(j = 0; j < ind2; ++j) {
      array[i][j] = (double*)(array + ind1 + ind1*ind2) + i*ind2*ind3 + j*ind3;
    }
  }
  return array;
}
1 голос
/ 11 декабря 2017

Вы заставляете себя воспринимать это как два принципиально разных способа выделения трехмерного массива. Это восприятие подкрепляется двумя точными отличительными деталями: 1) второй метод использует несколько уровней косвенности для доступа к фактическим элементам, 2) второй метод выделяет низкоуровневые массивы 1D независимо друг от друга .

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

int ***array3d = malloc(3 * sizeof(int **));
int **array2d = malloc(3 * 3 * sizeof(int *));
int *array1d = malloc(3 * 3 * 3 * sizeof(int));

for (size_t i = 0; i < 3; i++) 
{
  array3d[i] = array2d + i * 3;
  for (size_t j = 0; j < 3; j++)
    array3d[i][j] = array1d + i * 3 * 3 + j * 3;
}

array[1][2][1] = 10;

Если вы внимательно посмотрите на этот метод выделения, вы увидите, что в конце концов это почти то же самое, что и ваш второй метод: он строит трехуровневую структуру массива с использованием промежуточных указателей на каждом уровне косвенности. Единственное отличие состоит в том, что он заранее выделяет память для каждого уровня косвенности «заранее за один раз», вместо того, чтобы делать несколько повторяющихся вызовов malloc. Последующий цикл просто распределяет эту предварительно выделенную память среди подмассивов (то есть он просто инициализирует указатели).

Однако, если вы посмотрите еще ближе, вы также заметите, что фактическая память элемента массива (int, которая хранит фактические значения) распределяется точно так же, как это было бы в вашем первом методе: malloc(3 * 3 * 3 * sizeof(int)); - как простой плоский непрерывный массив.

Теперь, если вы подумаете об этом, вы должны понять, что этот третий метод не сильно отличается от вашего первого. Они оба используют плоский массив размером xSize * ySize * zSize для хранения данных. Единственным реальным отличием здесь является метод, который мы используем для расчета индекса для доступа к этим плоским данным. В первом методе мы вычислили бы индекс на лету как

array1d[z * ySize * xSize + y * xSize + x]

в третьем методе мы предварительно рассчитываем указатели на элементы массива заранее , используя по существу ту же формулу, сохраняем предварительно вычисленные результаты в дополнительных массивах и извлекаем их позже, используя "естественный" массив синтаксис доступа

array3d[x][y][x]

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

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

Также обратите внимание, что когда мы говорим о массивах с 3 и более измерениями, первый / второй / третий методы выделения могут использоваться вместе, одновременно для разных уровней косвенности. Вы можете решить реализовать 2D-массивы, используя первый метод, а затем объединить их в 3D-массив, используя второй метод.

1 голос
/ 10 января 2017

Ниже Код для 3d памяти:

int row3d = 4;
int column3d = 4;
int height3d =4;
int val3d =10;

int ***arr3d = (int***)malloc (row3d*sizeof(int**));
for (int i =0 ; i<column3d;i++)
{
    arr3d[i] = (int**)malloc (column3d*sizeof(int*));
    for (int j = 0;j<height3d;j++)
    {
        arr3d[i][j] = (int*)malloc (height3d*sizeof(int));

        for (int z =0;z<height3d;z++,val3d++)
        {
            arr3d[i][j][z]   = val3d;
        }
    }

}
// De allocation.
for (int i=0;i<row3d;i++)
{
    for(int j=0;j<column3d;j++)
    {
        free(arr3d[i][j]);
    }
}
free(arr3d);
arr3d = 0;
1 голос
/ 05 августа 2016

Надеюсь, это поможет вам !!!!

При выделении памяти для 2D-массива внутри 3D-массива назначьте выделенную память массиву [i], а не * array [i], и это будет работать без ошибки сегмента.

Вот ваша программа

int main () 
{
    int ***array = malloc(3*sizeof(int**));
    int i, j;

    for (i = 0; i < 3; i++) {
       array[i] = malloc(3*sizeof(int*));
       for (j = 0; j < 3; j++) {
          array[i][j] = malloc(3*sizeof(int));
       }
    }

    array[1][2][1] = 10;

    return 0;

}

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