Malloc трехмерный массив в C?

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

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?


person Mike    schedule 21.02.2010    source источник
comment
добавьте #include ‹stdlib.h› и удалите * из *array[i] и он запустится при компиляции в gcc   -  person Paul    schedule 21.02.2010
comment
Мне тоже интересно, как это реализовать. Решение с 1D-массивом чище (это то, что я использую до сих пор), однако для обработки чисел оно значительно медленнее, чем 3D со статическим распределением, из-за вычисления смещения.   -  person lmount    schedule 21.02.2010
comment
@WorldCitizeN, вы действительно измерили эту производительность? При доступе к статически выделенному массиву выполняются те же вычисления. Единственная разница в том, что вы их не пишете.   -  person charliehorse55    schedule 07.07.2012
comment
@lmount В общем, косвенность намного дороже, чем целочисленное умножение. Я ожидаю, что массив 1D будет быстрее.   -  person Daniel H    schedule 23.03.2018


Ответы (13)


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

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

int offset(int x, int y, int z) { 
    return (z * xSize * ySize) + (y * xSize) + x; 
}
person tim_yates    schedule 21.02.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, находит ith int ** указатель data[i]. Затем для заданного указателя int ** он перебирает его, находит jth 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;
}

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

person Alok Singhal    schedule 21.02.2010
comment
Привет, это, наверное, очень старо, но я хотел спросить: это p = malloc(xlen * sizeof *p)) эквивалентно p = (int**)malloc(xlen * sizeof(int*));? - person Vahagn Tumanyan; 18.01.2017

В 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];

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

person Johannes Schaub - litb    schedule 21.02.2010

Вы уверены, что вам нужно использовать 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
person Dims    schedule 02.12.2012
comment
Также я думаю, что можно лить из malloc, но не уверен, нужно проверить... - person Dims; 02.12.2012
comment
собственные многомерные массивы часто сталкиваются с ограничениями памяти - person christopherlovell; 18.11.2015
comment
@polyphant C выделяет точное место для элементов, без дополнительных данных - person Dims; 19.11.2015
comment
Размер переменных в стеке ограничен, а не в куче. См. здесь gribblelab.org/CBootcamp/7_Memory_Stack_vs_Heap.html - person christopherlovell; 19.11.2015
comment
Извините, неправильно вас понял, вы, конечно, правы. - person Dims; 19.11.2015

О, я ненавижу выделение массива 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;
}
person AndiDog    schedule 21.02.2010
comment
Верно, я просто привык это делать, потому что C++ выдаст ошибки, если вы этого не сделаете. - person AndiDog; 21.02.2010

Таким образом, вы можете выделить только 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;
}
person Alberto    schedule 09.07.2012

Вы заставляете себя воспринимать это как два принципиально разных способа размещения трехмерного массива. Это восприятие подкрепляется двумя определяющими отличительными деталями: 1) второй метод использует несколько уровней косвенности для доступа к фактическим элементам, 2) второй метод выделяет более низкий уровень 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-массив, используя второй метод.

person AnT    schedule 11.12.2017

Что касается segfault, я почти уверен, что кто-то еще указал на это, но на всякий случай в первой строке первого цикла 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));
        }
    }
person Malcolm McCullough    schedule 23.03.2018

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

При выделении памяти для 2D-массива внутри 3D-массива назначьте выделенную память массиву [i], а не *массиву[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;
}
person Quref    schedule 05.08.2016

Ниже кода для 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;
person ashutosh    schedule 10.01.2017

добавьте #include "stdlib.h" и удалите * из *array[i], и он будет работать при компиляции в gcc 4.4.1 на Ubuntu

также, если вы добавите операторы печати, вы сможете быстрее найти свои ошибки.

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

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

  printf("%s\n","OK");

  for (i = 0; i < 3; i++) {
    printf("i = %i \n",i);
    array[i] = malloc(3*sizeof(int*));
    for (j = 0; j < 3; j++) {
      printf("i,j = %i,%i \n",i,j);
      array[i][j] = malloc(3*sizeof(int));
    }
  }

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

  return 0;
}
person Paul    schedule 21.02.2010
comment
Операторы printf() могут быть полезны, но это не очень хорошая привычка. GDB на самом деле не так уж сложен в освоении и дает вам бесплатную печать и интроспекцию переменных в одном маленьком пакете. И вам даже не нужно заранее решать, куда поместить printf(). - person semisight; 29.11.2011

Чтобы понять процесс, я предлагаю вам выделить память для p, затем для p[0] и, наконец, для p[0][0], это простой пример:

   int ***a;
     a  = (int***)malloc(sizeof(int**));
    *a  = (int**)malloc(sizeof(int*));
   **a  = (int*)malloc(sizeof(int));
person OTMANE ER-RAGRAGUI    schedule 22.12.2020

Это должно работать, вы не приводите тип возвращаемого значения malloc

#include <stdio.h>

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

  for (i = 0; i < 3; i++) {
    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;
  printf("%d\n", array[1][2][1]);
  return 0;
}

Рабочая ссылка: http://ideone.com/X2mcb8

person bhargav22    schedule 12.11.2016