¿Cómo configuro, accedo y libero correctamente una matriz multidimensional en C?

10 minutos de lectura

He visto docenas de preguntas sobre “qué le pasa a mi código” con respecto a las matrices multidimensionales en C. Por alguna razón, parece que las personas no pueden entender lo que está sucediendo aquí, así que decidí responder esta pregunta como una referencia para otros. :

¿Cómo configuro, accedo y libero correctamente una matriz multidimensional en C?

Si otros tienen consejos útiles, ¡no dudes en publicarlos!

avatar de usuario
Miguel

Hablando estáticamenteesto es facil de entender:

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

Nada complicado aquí. 3 filas, 2 columnas; datos en la columna uno: 1, 2, 3; datos en la columna dos: 2, 3, 4. Podemos acceder a los elementos a través de la misma construcción:

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

Ahora veamos esto en términos de Punteros:

Los corchetes son una construcción muy buena para ayudar a simplificar las cosas, pero no ayudan cuando necesitamos trabajar en un entorno dinámico, por lo que debemos pensar en esto en términos de punteros. Si queremos almacenar una “fila” de enteros, necesitamos una matriz:

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

¿Y sabes qué? Podemos acceder a esto como un puntero.

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

Ahora, si no conocemos la cantidad de valores en una fila, podemos hacer que esta matriz tenga una longitud dinámica si tenemos un puntero a int y le damos algo de memoria:

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

Así que ahora tenemos una matriz dinámica de 1 dimensión; una sola fila. Pero queremos muchas filas, no solo una, y no sabemos cuántas. Eso significa que necesitamos otra matriz unidimensional dinámica, cada elemento de esa matriz será un puntero que apunta a una fila.

//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]

Ahora todo lo que queda por hacer es escribir el código que realizará estas asignaciones dinámicas:

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

Una de las cosas más importantes que debemos hacer ahora es asegurarnos de liberar la memoria cuando hayamos terminado. Cada nivel de malloc() debe tener el mismo número de free() llamadas, y las llamadas deben estar en un orden FILO (inverso de las llamadas 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; 

  • Buena respuesta, pero no use la sintaxis de puntero a puntero, crea multi-dim segmentado. matrices que no son compatibles con matrices asignadas estáticamente, ni con funciones de biblioteca estándar de C como memcpy, memset, bsearch, qsort, etc. Consulte la respuesta de Jens para conocer la forma preferida de asignar dinámicas multi-dim. arreglos

    – Lundin

    17/09/2012 a las 15:51

  • @Lundin: un gran punto, elegí usar la sintaxis de puntero a puntero ya que así es como me lo enseñaron en el pasado y creo que todavía se enseña de esa manera (basado en las preguntas que he visto en SO)

    – Miguel

    17 de septiembre de 2012 a las 16:03

  • No es “sintaxis”. La sintaxis son reglas sobre el lenguaje o, coloquialmente, una muestra particular de lenguaje. Los problemas de sintaxis son problemas de expresión y comunicación. El problema con el método de puntero a puntero no es simplemente el lenguaje que utiliza, sino las acciones inútiles que provoca en el programa: se usa más memoria de la necesaria (para los punteros innecesarios y para la contabilidad adicional cuando cada fila se asigna por separado) , se usa más tiempo del necesario (cargando un puntero cada vez que se accede a una fila y llamadas de asignación extra), y el código es más complejo de lo necesario.

    –Eric Postpischil

    17/09/2012 a las 16:15

  • @EricPostpischil Es sintaxis, porque el tipo utilizado es int** en vez de int (*)[].

    – Lundin

    17 de septiembre de 2012 a las 16:17

  • @Lundin: Eso es como decir que la diferencia entre París y una bomba termonuclear es la ortografía, porque una se escribe “París” y la otra se escribe “bomba termonuclear”. De hecho, no es la sintaxis la diferencia central o la diferencia con el mayor efecto. La sintaxis es sólo un medio de comunicación; lo que se comunica es el verdadero problema. Otra forma de ver esto es traducirlo a otro idioma: supongamos que se cambió la sintaxis pero el comportamiento subyacente sigue siendo el mismo. ¿Sería eso mejor? No, el problema del doble puntero se mantendría.

    –Eric Postpischil

    17/09/2012 a las 16:31

avatar de usuario
steve jesop

Hay al menos cuatro formas diferentes de crear o simular una matriz multidimensional en C89.

Uno es “asignar cada fila por separado”, descrito por Mike en su respuesta. Está no una matriz multidimensional, simplemente imita uno (en particular, imita la sintaxis para acceder a un elemento). Puede ser útil en el caso de que cada fila tenga un tamaño diferente, por lo que no está representando una matriz sino algo con un “borde irregular”.

Uno es “asignar una matriz multidimensional”. Se parece a esto:

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

Entonces la sintaxis para acceder al elemento. [i,j] es (*rows)[i][j]. En C89, ambos NUM_COLS y NUM_ROWS debe conocerse en tiempo de compilación. Esta es una verdadera matriz 2-D, y rows es un indicador de ello.

Una es, “asignar una matriz de filas”. Se parece a esto:

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

Entonces la sintaxis para acceder al elemento. [i,j] es rows[i][j]. En C89, NUM_COLS debe conocerse en tiempo de compilación. Esta es una verdadera matriz 2-D.

Una es, “asignar una matriz 1-d y fingir”. Se parece a esto:

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

Entonces la sintaxis para acceder al elemento. [i,j] es matrix[NUM_COLS * i + j]. Esto (por supuesto) no es una verdadera matriz 2-D. En la práctica tiene el mismo diseño que uno.

  • “asignar una matriz de filas”, ¿no es esto más bien: asignar una matriz de matrices, luego asignar un puntero de matriz para apuntar al primer objeto/matriz? Siempre uso esta forma yo mismo, aunque quizás el puntero “2D” es más estilísticamente correcto.

    – Lundin

    17/09/2012 a las 16:00

  • @Lundin: son ambos. En todas las formas (excepto posiblemente la matriz aplanada), cada fila es una matriz, por lo que una matriz de filas es una matriz de matrices. Pero dado que una matriz multidimensional es una matriz de matrices de todos modos (por definición en el estándar), mis títulos técnicamente no distinguen entre ellos. Para mí, la diferencia de énfasis es clara, tal vez para otros no.

    –Steve Jessop

    17 de septiembre de 2012 a las 16:02


  • Después de pensarlo un poco, definitivamente diría que la primera versión es preferible, porque permitiría la posibilidad de que un compilador o una herramienta de análisis estático imponga una “tipificación más fuerte”, al detectar y advertir contra conversiones de tipo incorrectas e implícitas. Las formas 2 y 3 podrían mezclarse accidentalmente con matrices 1D simples o punteros simples sin posibilidad de que ninguna herramienta detecte posibles errores.

    – Lundin

    17/09/2012 a las 16:37

  • Sin faltarle el respeto a su análisis, que creo que probablemente sea correcto, pero si prefiero algo, simplemente digo que lo prefiero, trato de recordar que no digo “prefiero”. Es posible que mis preocupaciones no sean las mismas que las de otra persona y, en particular, en C89, la necesidad de conocer los límites en tiempo de compilación es bastante limitante. La sintaxis de la primera opción no es tan atractiva, pero ciertamente permite que el compilador verifique los límites estáticos en ambas dimensiones en lugar de solo una.

    –Steve Jessop

    17/09/2012 a las 16:44


  • @mk..: el primero.

    –Steve Jessop

    14 de octubre de 2015 a las 7:31

En C desde C99, incluso las matrices multidimensionales dinámicas se pueden asignar fácilmente de una vez con malloc y liberado con 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);

  • Esta es la forma preferida, evite la sintaxis de puntero a puntero. No estoy seguro, pero creo que esto también funcionó en C90. ¿Seguramente los punteros de matriz existían antes de C99? Al menos las matrices “destrozadas” funcionaron, es decir double* A = malloc(x*y*sizeof(double));.

    – Lundin

    17/09/2012 a las 15:54

  • @Lundin, no, lamentablemente la parte de la declaración double (*A)[n] solo funcionó si n era una constante de tiempo de compilación, básicamente una macro o enum constante.

    – Jens Gusted

    17/09/2012 a las 15:56

  • Ajá, bueno, supongo que no tiene mucho sentido asignar dinámicamente con el tamaño conocido en tiempo de compilación 🙂 Aunque, ¿es obligatoria la ‘n’? no podrias escribir double (*A)[] = ?

    – Lundin

    17 de septiembre de 2012 a las 16:02

  • @Lundin: a veces tiene sentido asignar dinámicamente con el tamaño conocido en el momento de la compilación, porque una matriz multidimensional puede volar la pila con bastante facilidad.

    –Steve Jessop

    17/09/2012 a las 16:05

  • @JensGustedt ¿Puede devolver A desde una función y, de ser así, cuál es el tipo de devolución?

    – Patinete

    21 de septiembre de 2012 a las 16:43

Si desea utilizar una matriz definida por tipo, es aún más simple.

Digamos que tienes en tu código typedef int LabeledAdjMatrix[SIZE][SIZE];

A continuación, puede utilizar:

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

Entonces puedes escribir:

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

Porque pArr es un puntero a su matriz y * tiene menos prioridad que [];

Es por eso que un modismo común de modo es typedef la fila:

typedef int LabeledAdjRow[SIZE];

Entonces puedes escribir:

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

Y tu puedes memcpy todo eso directamente:

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

Saliendo de la respuesta de Jen, si desea asignar espacio para una matriz 3D, de la misma manera, puede hacerlo

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

¿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