Malloc una matriz tridimensional en C?

11 minutos de lectura

avatar de usuario
Miguel

Estoy traduciendo un código de MATLAB a C y el script que estoy convirtiendo hace un uso intensivo de matrices 3D con 10*100*300 entradas complejas. El tamaño de la matriz también depende de la entrada del sensor; idealmente, la matriz debe asignarse dinámicamente. Hasta ahora, he probado dos enfoques, el primero es una matriz 1D plana a lo largo de las líneas de

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

Lo que me duele el cerebro de usar. También probé una matriz de una matriz de punteros

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;
}

Lo que da una falla de segmentación cuando intento asignar datos.

En un mundo perfecto, me gustaría usar el segundo método con la notación de matriz para una programación más limpia y sencilla. ¿Hay una mejor manera de asignar dinámicamente una matriz tridimensional en C?

  • agregue #include y elimine el * de *array[i] y se ejecutará cuando se compile en gcc

    – Pablo

    21 de febrero de 2010 a las 14:46

  • También tengo curiosidad sobre cómo implementar esto. La solución de matriz 1D es “más limpia” (es la que uso hasta ahora), sin embargo, para el procesamiento de números es significativamente más lenta que la 3D asignada estáticamente, debido al cálculo de “compensación”.

    – lmount

    21 de febrero de 2010 a las 20:49

  • @WorldCitizeN, ¿realmente ha medido este rendimiento? Cuando accede a una matriz asignada estáticamente, se ejecutan los mismos cálculos. La única diferencia es que usted no los escribe.

    – charliehorse55

    7 julio 2012 a las 18:40

  • @lmount En general, la indirección es mucho más costosa que la multiplicación de enteros. Esperaría que la matriz 1D fuera más rápida.

    – Daniel H.

    23 de marzo de 2018 a las 19:56

avatar de usuario
tim_yates

Elegiría la primera opción (la matriz 1D única), ya que le dará un solo bloque de memoria para jugar en lugar de potencialmente miles de bloques de memoria fragmentados

Sin embargo, si acceder al elemento correcto de la matriz le molesta, escribiría un método de utilidad para convertir las ubicaciones x, y, z en un desplazamiento en la matriz 1D

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

Como han dicho otros, probablemente sea mejor asignar una porción contigua de memoria y luego descubrir la indexación usted mismo. Puede escribir una función para hacerlo si lo desea. Pero como pareces estar interesado en saber cómo lidiar con los múltiples malloc() caso, he aquí un ejemplo:

Primero, defino una función free_data()que libera un int *** con xlen y ylen como los dos primeros tamaños de dimensión. no necesitamos un zlen parámetro como free() no toma la longitud del puntero que se libera.

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);
}

La función recorre el puntero datadescubre el iel int ** puntero data[i]. Entonces, para un dado int ** puntero, lo recorre y encuentra el jel int * en data[i][j]y lo libera. También necesita liberar data[i] una vez que ha liberado todo data[i][j]y finalmente, necesita liberar data sí mismo.

Ahora a la función de asignación. La función es un poco complicada por la comprobación de errores. En particular, dado que hay 1 + xlen + xlen*ylen malloc llamadas, tenemos que ser capaces de manejar una falla en cualquiera de esas llamadas, y liberar toda la memoria que asignamos hasta ahora. Para facilitar las cosas, nos basamos en el hecho de que free(NULL) no es operativo, por lo que establecemos todos los punteros en un nivel dado igual a NULL antes de intentar asignarlos, de modo que si ocurre un error, podamos liberar todos los punteros.

Aparte de eso, la función es bastante simple. Primero asignamos espacio para xlen int ** valores, entonces para cada uno de esos xlen punteros, asignamos espacio para ylen int * valores, y luego para cada uno de esos xlen*ylen punteros, asignamos espacio para zlen int valores, dándonos un espacio total para xlen*ylen*zlen int valores:

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;
}

Tenga en cuenta que he simplificado malloc llama un poco: en general, no debe emitir el valor de retorno de mallocy especifique el objeto que está asignando como el operando para sizeof operador en lugar de su tipo. Lo que hace malloc llamadas más sencillas de escribir y menos propensas a errores. necesitas incluir stdlib.h por malloc.

Aquí hay un programa de prueba que usa las dos funciones anteriores:

#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;
}

Por supuesto, use este enfoque si le resulta más fácil usarlo. En general, esto será más lento que usar una porción contigua de memoria, pero si encuentra que la velocidad está bien con el esquema anterior, y si le hace la vida más fácil, puede seguir usándolo. Incluso si no lo usa, es bueno saber cómo hacer que funcione un esquema de este tipo.

  • Hola, esto es probablemente muy antiguo, pero quería preguntar: ¿Es esto p = malloc(xlen * sizeof *p)) equivalente a p = (int**)malloc(xlen * sizeof(int*)); ?

    – Vahagn Tumanian

    18 de enero de 2017 a las 14:47

avatar de usuario
atenúa

¿Estás seguro de que necesitas usar malloc? C permite la creación de matrices multidimensionales de forma nativa:

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

O puedes usar malloc de la siguiente manera:

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

  • También creo que es posible lanzar desde mallocpero no estoy seguro, necesito comprobar…

    – Atenúa

    2 de diciembre de 2012 a las 18:39

  • las matrices multidimensionales nativas a menudo se enfrentan a limitaciones de memoria

    – christopherlovell

    18 de noviembre de 2015 a las 16:49

  • @polyphant C asigna el espacio exacto para los elementos, sin datos adicionales

    – Atenúa

    19 de noviembre de 2015 a las 7:17

  • Las variables en la pila tienen un tamaño limitado, no en el montón. Mira aquí gribblelab.org/CBootcamp/7_Memory_Stack_vs_Heap.html

    – christopherlovell

    19 de noviembre de 2015 a las 10:54

  • Lo siento, te entendí mal, tienes razón por supuesto.

    – Atenúa

    19 de noviembre de 2015 a las 20:24

No hay forma en C89 de hacer lo que desea, porque un tipo de matriz en C solo se puede especificar con valores conocidos de tiempo de compilación. Entonces, para evitar la loca asignación dinámica, tendrá que apegarse a la forma unidimensional. Puede usar una función para facilitar este proceso

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

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

En C99 puede usar una sintaxis de matriz común incluso si las dimensiones son valores de tiempo de ejecución:

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

Sin embargo, solo funciona con arreglos locales no estáticos.

Oh, odio la asignación de matrices malloc ^^

Aquí hay una versión correcta, básicamente era solo una línea incorrecta:

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;
}

  • Correcto, estoy acostumbrado a hacerlo porque C ++ generará errores si no lo hace.

    – AndiDog

    21 de febrero de 2010 a las 19:01

avatar de usuario
Hormiga

Se está obligando a percibir esto como dos formas fundamentalmente diferentes de asignar una matriz 3D. Esta percepción se ve reforzada por dos detalles diferenciadores definitivos: 1) el segundo método utiliza varios niveles de indirección para acceder a los elementos reales, 2) el segundo método asigna las matrices 1D de nivel inferior independientemente.

Pero, ¿por qué exactamente insiste en asignar las matrices 1D de nivel inferior independientemente? No tienes que hacer eso. Y una vez que lo tenga en cuenta, debe darse cuenta de que existe un tercer método para construir su matriz 3D

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;

Si observa este método de asignación de cerca, debería ver que al final es más o menos lo mismo que su segundo método: crea una estructura de matriz de tres niveles mediante el uso de punteros intermedios en cada nivel de direccionamiento indirecto. La única diferencia es que preasigna memoria para cada nivel de direccionamiento indirecto de forma contigua, “de una vez”, de antemano, en lugar de hacer múltiples repeticiones. malloc llamadas El ciclo subsiguiente simplemente distribuye esa memoria preasignada entre los subconjuntos (es decir, simplemente inicializa los punteros).

Sin embargo, si mira aún más de cerca, también notará que la memoria del elemento de matriz real (el ints que almacenan los valores reales) se asignan exactamente de la misma manera que lo harían en su primer método: malloc(3 * 3 * 3 * sizeof(int)); – como una matriz contigua plana simple.

Ahora, si lo piensa, debe darse cuenta de que este tercer método no es muy diferente del primero. Ambos usan una matriz plana de tamaño xSize * ySize * zSize para almacenar los datos. La única diferencia real aquí es el método que usamos para calcular el índice para acceder a esos datos planos. En el primer método, calcularíamos el índice sobre la marcha como

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

en el tercer método, precalculamos los punteros a los elementos de la matriz por adelantadousando esencialmente la misma fórmula, almacene los resultados precalculados en matrices adicionales y recupérelos más tarde usando la sintaxis de acceso a la matriz “natural”.

array3d[x][y][x]

La pregunta aquí es si este cálculo previo vale la pena el esfuerzo adicional y la memoria adicional. La respuesta es: generalmente no, no lo es. Al gastar esta memoria adicional, no obtendrá ningún beneficio de rendimiento apreciable (lo más probable es que haga que su código sea más lento).

La única situación en la que podría valer la pena considerar el segundo método es cuando se trata de dentado/irregular matriz: una matriz multidimensional escasa con algunas partes de sub-matrices que faltan/no se usan o tienen un tamaño reducido. Por ejemplo, si se sabe que algunos subconjuntos 1D o 2D de su conjunto 3D contienen solo ceros, puede decidir no almacenarlos en la memoria y establecer los punteros correspondientes en nulo. Esto implicaría usar su segundo método, donde los subconjuntos se asignan (o no se asignan) de forma independiente. Si los datos son grandes, el ahorro de memoria resultante podría valer la pena.

También tenga en cuenta que cuando hablamos de arreglos con 3 y más dimensiones, los métodos de asignación primero/segundo/tercero se pueden usar juntos, simultáneamente para diferentes niveles de direccionamiento indirecto. Puede decidir implementar arreglos 2D usando el primer método y luego combinarlos en un arreglo 3D usando el segundo método.

  • Correcto, estoy acostumbrado a hacerlo porque C ++ generará errores si no lo hace.

    – AndiDog

    21 de febrero de 2010 a las 19:01

avatar de usuario
Alberto

De esta manera, puede asignar solo 1 bloque de memoria y la matriz dinámica se comporta como la estática (es decir, la misma contigüidad de memoria). También puede liberar memoria con un solo libre (arreglo) como los arreglos 1-D ordinarios.

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;
}

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad