¿Cómo puedo trabajar con matrices arbitrariamente dimensionales asignadas dinámicamente?

La matriz 1-D típica se puede asignar de forma estática o automática en una statement.

enum { n=100 }; int arr1[n]; 

O asignada dinámicamente y accedida a través de un puntero.

 int *arr1m=malloc(n*sizeof*arr1m); int *arr1c=calloc(n, sizeof*arr1c); 

Ambos estilos acceden a un elemento con la misma syntax.

 int i = n/2; arr1[i] = arr1c[i] = arr1m[i] = 42; 

Pero cuando agrega una segunda dimensión, requiere cierto esfuerzo lograr la misma syntax.

 int arr2[n][n]; int *arr2c=calloc(n*n,sizeof*arr2c); arr2[5][5] = arr2c[5*n+5] = 23; 

Solo puede obtener el doble conjunto de corchetes si en su lugar lo construye como un vector Iliffe .

 int **arr2l=calloc(n,sizeof*arr2l); for (int j=0; j<n; j++) arr2l[j]=calloc(n,sizeof**arr2l); arr2[6][6] = arr2l[6][6] = 72; 

Pero esto se vuelve cada vez más engorroso a medida que aumentan las dimensiones.

Otra dificultad es verificar los límites de una matriz dinámica antes de acceder a un elemento (para que no esté tocando la memoria que no se asignó correctamente). Las matrices reales pueden usar el operador sizeof para determinar los límites, pero ninguna de estas matrices dinámicas llevan consigo su tamaño.

¿Cómo puedo definir una estructura que tenga un diseño rápido y contiguo como una matriz, pero con una syntax consistente para acceder a los elementos con una lista de índices que funciona igual para una matriz 2D que para una matriz 3D? y todo dinámicamente, con tamaños disponibles dinámicamente para que puedan pasar y regresar de las funciones?

No hay necesidad de reinventar la rueda, C tiene que desde C99, se llama matriz de longitud variable, VLA. Tiene solo la syntax como arreglos d-dimensionales “normales”, solo que los límites pueden ser variables y no están permitidos en el scope del archivo.

Como tales objetos pueden volverse relativamente grandes, no deberías asignarlos a la stack, sino con algo como malloc

 double (*A)[n][m] = malloc(sizeof(double[k][n][m])); 

El comstackdor luego le ayuda con todos los cálculos de índice sin problemas. Si desea pasar tales animales a funciones, solo debe tener cuidado de declarar los límites primero:

 void func(size_t k, size_t n, size_t m, double A[k][n][m]); 

Esto hace que su intención sea clara para ambos, el lector humano y el comstackdor. Prefiero esto sobre la forma equivalente

 void func(size_t k, size_t n, size_t m, double (*A)[n][m]); 

Si define a como un puntero a una matriz de n enteros, el comstackdor realizará la aritmética del índice.

 #define N 7 int (*a)[N]; int main() { a = malloc(N*N*sizeof(int)); a[2][3] = 0; } 

ADICIONAL:

Del mismo modo, un ejemplo tridimensional:

 #include  #include  #define N 7 int (*a)[N][N]; int main() { int i,j,k; a = malloc(N*N*N*sizeof(int)); for(i=0; i 

Existe una estructura de datos utilizada en la implementación del lenguaje J (un dialecto de APL) que acomoda matrices de dimensión arbitraria asignadas dinámicamente. Utiliza un híbrido de una struct y una matriz dinámica para su estructura de datos, un truco conocido comúnmente como struct hack . (Más información sobre la implementación de J aquí y aquí ).

Para ver la idea en un contexto simple, considere el caso 1D: queremos una matriz 1D dinámica que lleve su tamaño junto con ella. Asi que:

 struct vec { int n; int p[]; }; 

Como el miembro p es el último y C no tiene una verificación de límites incorporada, se puede usar para acceder a la memoria adicional al final de la struct . Por supuesto, al asignar, necesitaremos proporcionar esta memoria adicional y no simplemente asignar el tamaño de la struct . La struct es solo el encabezado de la matriz. C90 requiere un número (por ejemplo, 1) para la longitud de la matriz p [], pero C99 permite omitir el número por lo que el tamaño del encabezado es más fácil de calcular.

Por lo tanto, una matriz con más dimensiones necesitará más valores para contener los tamaños de cada dimensión. Y para que nuestra estructura acomode matrices de diferente dimensionalidad, este vector de dimensión necesitará ser también de longitud variable.

Lo que podemos hacer para lograr todo esto es aplicar el struct hack dos veces, recursivamente sobre sí mismo. Esto nos da un diseño de memoria como este, donde R es el número de dimensiones que llamaremos el rango de la matriz, los valores D son las longitudes de cada dimensión, y los valores V son los datos reales de la matriz:

  1 R Product(D) --- -------------------- ----------------------------- RD[0] D[1] ... D[R-1] V[0] V[1] ... V[Product(D)-1] 

Y para describir esto en C,

 typedef struct arr { int r; int d[]; } *arr; 

Los elementos de una matriz siguen inmediatamente los elementos R del vector Dims D Por lo tanto, se puede acceder a los elementos V en a->d[r+0], a->d[r+1], ... a->d[r+i] (después de reducir el vector de índice a un solo índice en la representación aplanada). Los elementos son más fáciles de tratar en orden mayor de fila. El número de elementos reales es el producto de todas las dimensiones, todas las dimensiones se multiplican juntas. Editar: La expresión aquí podría estar mejor escrita: (a->d+a->r)[0], (a->d+a->r)[1], ... (a->d+a->r)[i] .

Para asignar una de estas cosas, necesitaremos una función para calcular este producto como parte del cálculo del tamaño.

 int productdims(int rank, int *dims){ int z=1; for(int i=0; i 

Y para inicializar, solo necesitamos completar los miembros.

 arr makearr(int rank, int *dims){ arr z = calloc( (sizeof(struct arr)/sizeof(int)) + rank + productdims(rank,dims), sizeof(int)); z->r = rank; memmove(z->d,dims,rank*sizeof(int)); return z; } 

Recordando la fórmula para acceder a los datos 2D (digamos una matriz de [m] [n] elementos) con un solo índice (es una matriz dinámica típica como en la pregunta). El elemento [i] [j] está en i × n & plus; j . Con una matriz 3D [m] [n] [o], el elemento [i] [j] [k] está en i × (n × o) + j × o & plus; k .

De modo que podemos calcular un único índice para nuestros datos linealmente distribuidos a partir de una matriz de índices y la matriz de dimensiones.

 int *elem(arr a, ...){ va_list ap; int idx = 0; va_start(ap,a); if (a->r){ idx = va_arg(ap,int); for(int i=1; ir; i++){ idx *= a->d[i]; idx += va_arg(ap,int); } } va_end(ap); return &a->d[a->r + idx]; } 

No olvides los encabezados:

 #include  #include  #include  #include  

Y voilá:

 int main() { { int i,n=6; arr m = makearr(1, (int[]){n}); for (i=0;i 

Salida:

 0 1 2 3 4 5 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 

La elaboración adicional de este código para admitir la multiplicación de matrices 2D y las divisiones de columna se ha publicado en comp.lang.c en este hilo .

Pregunta mejor motivada / estructura de datos más poderosa .